Settings rework...
This commit is contained in:
parent
74b233798d
commit
fb68300746
63 changed files with 7139 additions and 1026 deletions
|
|
@ -1,9 +1,9 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
|
||||
PanelWindow {
|
||||
id: activeWindowPanel
|
||||
|
|
@ -14,7 +14,7 @@ PanelWindow {
|
|||
anchors.right: true
|
||||
focusable: false
|
||||
margins.top: barHeight
|
||||
visible: !activeWindowWrapper.finallyHidden
|
||||
visible: Settings.settings.showActiveWindow && !activeWindowWrapper.finallyHidden
|
||||
implicitHeight: activeWindowTitleContainer.height
|
||||
implicitWidth: 0
|
||||
property int barHeight: 36
|
||||
|
|
@ -121,6 +121,7 @@ PanelWindow {
|
|||
source: ToplevelManager?.activeToplevel ? getIcon() : ""
|
||||
visible: Settings.settings.showActiveWindowIcon
|
||||
anchors.verticalCenterOffset: -3
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
|
|
|
|||
|
|
@ -3,18 +3,159 @@ import QtQuick.Controls
|
|||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
import Quickshell.Wayland
|
||||
|
||||
import "../../Helpers/Fuzzysort.js" as Fuzzysort
|
||||
|
||||
PanelWithOverlay {
|
||||
Timer {
|
||||
id: clipboardTimer
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: appLauncherPanel.visible && searchField.text.startsWith(">clip")
|
||||
onTriggered: {
|
||||
updateClipboardHistory();
|
||||
}
|
||||
}
|
||||
|
||||
property var clipboardHistory: []
|
||||
property bool clipboardInitialized: false
|
||||
|
||||
Process {
|
||||
id: clipboardTypeProcess
|
||||
property bool isLoading: false
|
||||
property var currentTypes: []
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
currentTypes = String(stdout.text).trim().split('\n').filter(t => t);
|
||||
|
||||
const imageType = currentTypes.find(t => t.startsWith('image/'));
|
||||
if (imageType) {
|
||||
clipboardImageProcess.mimeType = imageType;
|
||||
clipboardImageProcess.command = ["sh", "-c", `wl-paste -n -t "${imageType}" | base64 -w 0`];
|
||||
clipboardImageProcess.running = true;
|
||||
} else {
|
||||
|
||||
clipboardHistoryProcess.command = ["wl-paste", "-n", "--type", "text/plain"];
|
||||
clipboardHistoryProcess.running = true;
|
||||
}
|
||||
} else {
|
||||
|
||||
clipboardTypeProcess.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: clipboardImageProcess
|
||||
property string mimeType: ""
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
const base64 = stdout.text.trim();
|
||||
if (base64) {
|
||||
const entry = {
|
||||
type: 'image',
|
||||
mimeType: mimeType,
|
||||
data: `data:${mimeType};base64,${base64}`,
|
||||
timestamp: new Date().getTime()
|
||||
};
|
||||
|
||||
|
||||
const exists = clipboardHistory.find(item =>
|
||||
item.type === 'image' && item.data === entry.data
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
clipboardHistory = [entry, ...clipboardHistory].slice(0, 20);
|
||||
root.updateFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!clipboardHistoryProcess.isLoading) {
|
||||
clipboardInitialized = true;
|
||||
}
|
||||
clipboardTypeProcess.isLoading = false;
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: clipboardHistoryProcess
|
||||
property bool isLoading: false
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode === 0) {
|
||||
const content = String(stdout.text).trim();
|
||||
if (content && !content.startsWith("vscode-file://")) {
|
||||
const entry = {
|
||||
type: 'text',
|
||||
content: content,
|
||||
timestamp: new Date().getTime()
|
||||
};
|
||||
|
||||
|
||||
const exists = clipboardHistory.find(item => {
|
||||
if (item.type === 'text') {
|
||||
return item.content === content;
|
||||
}
|
||||
|
||||
return item === content;
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
|
||||
const newHistory = clipboardHistory.map(item => {
|
||||
if (typeof item === 'string') {
|
||||
return {
|
||||
type: 'text',
|
||||
content: item,
|
||||
timestamp: new Date().getTime()
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
clipboardHistory = [entry, ...newHistory].slice(0, 20);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
clipboardHistoryProcess.isLoading = false;
|
||||
}
|
||||
clipboardInitialized = true;
|
||||
clipboardTypeProcess.isLoading = false;
|
||||
root.updateFilter();
|
||||
}
|
||||
|
||||
stdout: StdioCollector {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function updateClipboardHistory() {
|
||||
if (!clipboardTypeProcess.isLoading && !clipboardHistoryProcess.isLoading) {
|
||||
clipboardTypeProcess.isLoading = true;
|
||||
clipboardTypeProcess.command = ["wl-paste", "-l"];
|
||||
clipboardTypeProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
id: appLauncherPanel
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
function isPinned(app) {
|
||||
return app && app.execString && Settings.settings.pinnedExecs.indexOf(app.execString) !== -1;
|
||||
}
|
||||
|
||||
function togglePin(app) {
|
||||
if (!app || !app.execString) return;
|
||||
var arr = Settings.settings.pinnedExecs ? Settings.settings.pinnedExecs.slice() : [];
|
||||
|
|
@ -101,9 +242,11 @@ PanelWithOverlay {
|
|||
appLauncherPanel.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
function isMathExpression(str) {
|
||||
return /^[-+*/().0-9\s]+$/.test(str);
|
||||
}
|
||||
|
||||
function safeEval(expr) {
|
||||
try {
|
||||
return Function('return (' + expr + ')')();
|
||||
|
|
@ -111,13 +254,114 @@ PanelWithOverlay {
|
|||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilter() {
|
||||
var query = searchField.text ? searchField.text.toLowerCase() : "";
|
||||
var apps = root.appModel.slice();
|
||||
var results = [];
|
||||
// Calculator mode: starts with '='
|
||||
if (query.startsWith("=")) {
|
||||
var expr = searchField.text.slice(1).trim();
|
||||
|
||||
|
||||
if (query === ">") {
|
||||
results.push({
|
||||
isCommand: true,
|
||||
name: ">calc",
|
||||
content: "Calculator - evaluate mathematical expressions",
|
||||
icon: "calculate",
|
||||
execute: function() {
|
||||
searchField.text = ">calc ";
|
||||
searchField.cursorPosition = searchField.text.length;
|
||||
}
|
||||
});
|
||||
|
||||
results.push({
|
||||
isCommand: true,
|
||||
name: ">clip",
|
||||
content: "Clipboard history - browse and restore clipboard items",
|
||||
icon: "content_paste",
|
||||
execute: function() {
|
||||
searchField.text = ">clip ";
|
||||
searchField.cursorPosition = searchField.text.length;
|
||||
}
|
||||
});
|
||||
|
||||
root.filteredApps = results;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (query.startsWith(">clip")) {
|
||||
if (!clipboardInitialized) {
|
||||
updateClipboardHistory();
|
||||
}
|
||||
const searchTerm = query.slice(5).trim();
|
||||
|
||||
clipboardHistory.forEach(function(clip, index) {
|
||||
let searchContent = clip.type === 'image' ?
|
||||
clip.mimeType :
|
||||
clip.content || clip; // Support both new object format and old string format
|
||||
|
||||
if (!searchTerm || searchContent.toLowerCase().includes(searchTerm)) {
|
||||
let entry;
|
||||
if (clip.type === 'image') {
|
||||
entry = {
|
||||
isClipboard: true,
|
||||
name: "Image from " + new Date(clip.timestamp).toLocaleTimeString(),
|
||||
content: "Image: " + clip.mimeType,
|
||||
icon: "image",
|
||||
type: 'image',
|
||||
data: clip.data,
|
||||
execute: function() {
|
||||
// Convert base64 image data back to binary and copy to clipboard
|
||||
const base64Data = clip.data.split(',')[1];
|
||||
clipboardTypeProcess.command = ["sh", "-c", `echo '${base64Data}' | base64 -d | wl-copy -t '${clip.mimeType}'`];
|
||||
clipboardTypeProcess.running = true;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
const textContent = clip.content || clip; // Support both new object format and old string format
|
||||
let displayContent = textContent;
|
||||
let previewContent = "";
|
||||
|
||||
// Clean up whitespace for display
|
||||
displayContent = displayContent.replace(/\s+/g, ' ').trim();
|
||||
|
||||
// Truncate long content and show preview
|
||||
if (displayContent.length > 50) {
|
||||
previewContent = displayContent;
|
||||
// Show first line or first 50 characters as title
|
||||
displayContent = displayContent.split('\n')[0].substring(0, 50) + "...";
|
||||
}
|
||||
|
||||
entry = {
|
||||
isClipboard: true,
|
||||
name: displayContent,
|
||||
content: previewContent || textContent,
|
||||
icon: "content_paste",
|
||||
execute: function() {
|
||||
Quickshell.execDetached(["sh", "-c", "echo -n '" + textContent.replace(/'/g, "'\\''") + "' | wl-copy"]);
|
||||
}
|
||||
};
|
||||
}
|
||||
results.push(entry);
|
||||
}
|
||||
});
|
||||
|
||||
if (results.length === 0) {
|
||||
results.push({
|
||||
isClipboard: true,
|
||||
name: "No clipboard history",
|
||||
content: "No matching clipboard entries found",
|
||||
icon: "content_paste_off"
|
||||
});
|
||||
}
|
||||
|
||||
root.filteredApps = results;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (query.startsWith(">calc")) {
|
||||
var expr = searchField.text.slice(5).trim();
|
||||
if (expr && isMathExpression(expr)) {
|
||||
var value = safeEval(expr);
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
|
|
@ -130,8 +374,27 @@ PanelWithOverlay {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var pinned = [];
|
||||
var unpinned = [];
|
||||
for (var i = 0; i < results.length; ++i) {
|
||||
var app = results[i];
|
||||
if (app.execString && Settings.settings.pinnedExecs.indexOf(app.execString) !== -1) {
|
||||
pinned.push(app);
|
||||
} else {
|
||||
unpinned.push(app);
|
||||
}
|
||||
}
|
||||
// Sort pinned apps alphabetically for consistent display
|
||||
pinned.sort(function(a, b) {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
});
|
||||
root.filteredApps = pinned.concat(unpinned);
|
||||
root.selectedIndex = 0;
|
||||
return;
|
||||
}
|
||||
if (!query || query.startsWith("=")) {
|
||||
if (!query) {
|
||||
results = results.concat(apps.sort(function (a, b) {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
}));
|
||||
|
|
@ -143,7 +406,7 @@ PanelWithOverlay {
|
|||
return r.obj;
|
||||
}));
|
||||
}
|
||||
// Pinning logic: split into pinned and unpinned
|
||||
|
||||
var pinned = [];
|
||||
var unpinned = [];
|
||||
for (var i = 0; i < results.length; ++i) {
|
||||
|
|
@ -161,10 +424,12 @@ PanelWithOverlay {
|
|||
root.filteredApps = pinned.concat(unpinned);
|
||||
root.selectedIndex = 0;
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (filteredApps.length > 0)
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredApps.length - 1);
|
||||
}
|
||||
|
||||
function selectPrev() {
|
||||
if (filteredApps.length > 0)
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||
|
|
@ -182,6 +447,10 @@ PanelWithOverlay {
|
|||
Quickshell.clipboardText = String(modelData.result);
|
||||
Quickshell.execDetached(["notify-send", "Calculator Result", `${modelData.expr} = ${modelData.result} (copied to clipboard)`]);
|
||||
});
|
||||
} else if (modelData.isCommand) {
|
||||
|
||||
modelData.execute();
|
||||
return;
|
||||
} else if (modelData.runInTerminal && termEmu){
|
||||
Quickshell.execDetached([termEmu, "-e", modelData.execString.trim()]);
|
||||
} else if (modelData.execute) {
|
||||
|
|
@ -200,310 +469,382 @@ PanelWithOverlay {
|
|||
|
||||
Component.onCompleted: updateFilter()
|
||||
|
||||
ColumnLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 32
|
||||
spacing: 18
|
||||
|
||||
// Search Bar
|
||||
|
||||
Rectangle {
|
||||
id: searchBar
|
||||
color: Theme.surfaceVariant
|
||||
radius: 22
|
||||
height: 48
|
||||
Layout.fillWidth: true
|
||||
border.color: searchField.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: searchField.activeFocus ? 2 : 1
|
||||
id: previewPanel
|
||||
Layout.preferredWidth: 200
|
||||
Layout.fillHeight: true
|
||||
color: Theme.surface
|
||||
radius: 20
|
||||
visible: false
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 14
|
||||
anchors.rightMargin: 14
|
||||
spacing: 10
|
||||
Text {
|
||||
text: "search"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeHeader
|
||||
color: searchField.activeFocus ? Theme.accentPrimary : Theme.textSecondary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
TextField {
|
||||
id: searchField
|
||||
placeholderText: "Search apps..."
|
||||
color: Theme.textPrimary
|
||||
placeholderTextColor: Theme.textSecondary
|
||||
background: null
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onTextChanged: root.updateFilter()
|
||||
selectedTextColor: Theme.onAccent
|
||||
selectionColor: Theme.accentPrimary
|
||||
padding: 0
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
font.bold: true
|
||||
Component.onCompleted: contentItem.cursorColor = Theme.textPrimary
|
||||
onActiveFocusChanged: contentItem.cursorColor = Theme.textPrimary
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Keys.onDownPressed: root.selectNext()
|
||||
Keys.onUpPressed: root.selectPrev()
|
||||
Keys.onEnterPressed: root.activateSelected()
|
||||
Keys.onReturnPressed: root.activateSelected()
|
||||
Keys.onEscapePressed: appLauncherPanel.hidePanel()
|
||||
}
|
||||
}
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
Image {
|
||||
id: previewImage
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
cache: true
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// App List Card
|
||||
Rectangle {
|
||||
color: Theme.surface
|
||||
radius: 20
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
property int innerPadding: 16
|
||||
spacing: 18
|
||||
|
||||
Item {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: parent.innerPadding
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
id: searchBar
|
||||
color: Theme.surfaceVariant
|
||||
radius: 20
|
||||
height: 48
|
||||
Layout.fillWidth: true
|
||||
border.color: searchField.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: searchField.activeFocus ? 2 : 1
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 14
|
||||
anchors.rightMargin: 14
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
text: "search"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeHeader
|
||||
color: searchField.activeFocus ? Theme.accentPrimary : Theme.textSecondary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: searchField
|
||||
placeholderText: "Search apps..."
|
||||
color: Theme.textPrimary
|
||||
placeholderTextColor: Theme.textSecondary
|
||||
background: null
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onTextChanged: root.updateFilter()
|
||||
selectedTextColor: Theme.onAccent
|
||||
selectionColor: Theme.accentPrimary
|
||||
padding: 0
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
font.bold: true
|
||||
Component.onCompleted: contentItem.cursorColor = Theme.textPrimary
|
||||
onActiveFocusChanged: contentItem.cursorColor = Theme.textPrimary
|
||||
|
||||
Keys.onDownPressed: root.selectNext()
|
||||
Keys.onUpPressed: root.selectPrev()
|
||||
Keys.onEnterPressed: root.activateSelected()
|
||||
Keys.onReturnPressed: root.activateSelected()
|
||||
Keys.onEscapePressed: appLauncherPanel.hidePanel()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: appList
|
||||
anchors.fill: parent
|
||||
anchors.margins: parent.innerPadding
|
||||
spacing: 2
|
||||
model: root.filteredApps
|
||||
currentIndex: root.selectedIndex
|
||||
delegate: Item {
|
||||
id: appDelegate
|
||||
width: appList.width
|
||||
height: 48
|
||||
property bool hovered: mouseArea.containsMouse
|
||||
property bool isSelected: index === root.selectedIndex
|
||||
|
||||
Rectangle {
|
||||
color: Theme.surface
|
||||
radius: 20
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
property int innerPadding: 16
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: (hovered || isSelected)
|
||||
? Theme.accentPrimary
|
||||
: (appLauncherPanel.isPinned(modelData) ? Theme.surfaceVariant : "transparent")
|
||||
radius: 12
|
||||
border.color: appLauncherPanel.isPinned(modelData)
|
||||
? "transparent"
|
||||
: (hovered || isSelected ? Theme.accentPrimary : "transparent")
|
||||
border.width: appLauncherPanel.isPinned(modelData) ? 0 : (hovered || isSelected ? 2 : 0)
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
}
|
||||
ListView {
|
||||
id: appList
|
||||
anchors.fill: parent
|
||||
anchors.margins: parent.innerPadding
|
||||
spacing: 2
|
||||
model: root.filteredApps
|
||||
currentIndex: root.selectedIndex
|
||||
delegate: Item {
|
||||
id: appDelegate
|
||||
width: appList.width
|
||||
height: (modelData.isClipboard || modelData.isCommand) ? 64 : 48
|
||||
property bool hovered: mouseArea.containsMouse
|
||||
property bool isSelected: index === root.selectedIndex
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
spacing: 10
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: (hovered || isSelected)
|
||||
? Theme.accentPrimary
|
||||
: (appLauncherPanel.isPinned(modelData) ? Theme.surfaceVariant : "transparent")
|
||||
radius: 12
|
||||
border.color: appLauncherPanel.isPinned(modelData)
|
||||
? "transparent"
|
||||
: (hovered || isSelected ? Theme.accentPrimary : "transparent")
|
||||
border.width: appLauncherPanel.isPinned(modelData) ? 0 : (hovered || isSelected ? 2 : 0)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.width {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
spacing: 10
|
||||
|
||||
Item {
|
||||
width: 28
|
||||
height: 28
|
||||
property bool iconLoaded: !modelData.isCalculator && !modelData.isClipboard && !modelData.isCommand && iconImg.status === Image.Ready && iconImg.source !== "" && iconImg.status !== Image.Error
|
||||
|
||||
Image {
|
||||
id: clipboardImage
|
||||
anchors.fill: parent
|
||||
visible: modelData.type === 'image'
|
||||
source: modelData.data || ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse && modelData.type === 'image') {
|
||||
previewImage.source = modelData.data;
|
||||
previewPanel.visible = true;
|
||||
} else {
|
||||
previewPanel.visible = false;
|
||||
}
|
||||
}
|
||||
onMouseXChanged: mouse.accepted = false
|
||||
onMouseYChanged: mouse.accepted = false
|
||||
onClicked: mouse.accepted = false
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
source: modelData.isCalculator ? "qrc:/icons/calculate.svg" :
|
||||
modelData.isClipboard ? "qrc:/icons/" + modelData.icon + ".svg" :
|
||||
modelData.isCommand ? "qrc:/icons/" + modelData.icon + ".svg" :
|
||||
Quickshell.iconPath(modelData.icon, "application-x-executable")
|
||||
visible: (modelData.isCalculator || modelData.isClipboard || modelData.isCommand || parent.iconLoaded) && modelData.type !== 'image'
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !modelData.isCalculator && !modelData.isClipboard && !modelData.isCommand && !parent.iconLoaded && modelData.type !== 'image'
|
||||
text: "broken_image"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeHeader
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 1
|
||||
|
||||
Text {
|
||||
text: modelData.name
|
||||
color: (hovered || isSelected) ? Theme.onAccent : (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textPrimary)
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.bold: hovered || isSelected
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.isCalculator ? (modelData.expr + " = " + modelData.result) :
|
||||
modelData.isClipboard ? modelData.content :
|
||||
modelData.isCommand ? modelData.content :
|
||||
(modelData.comment || modelData.genericName || "No description available")
|
||||
color: (hovered || isSelected) ? Theme.onAccent : (appLauncherPanel.isPinned(modelData) ? Theme.textSecondary : Theme.textSecondary)
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
font.italic: !(modelData.comment || modelData.genericName)
|
||||
opacity: modelData.isClipboard ? 0.8 : modelData.isCommand ? 0.9 : ((modelData.comment || modelData.genericName) ? 1.0 : 0.6)
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: (modelData.isClipboard || modelData.isCommand) ? 2 : 1
|
||||
wrapMode: (modelData.isClipboard || modelData.isCommand) ? Text.WordWrap : Text.NoWrap
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: (modelData.isClipboard || modelData.isCommand) ? implicitHeight : contentHeight
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.isCalculator ? "content_copy" : "chevron_right"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
color: (hovered || isSelected)
|
||||
? Theme.onAccent
|
||||
: (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textSecondary)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.rightMargin: 8
|
||||
}
|
||||
|
||||
|
||||
Item { width: 8; height: 1 }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: ripple
|
||||
anchors.fill: parent
|
||||
color: Theme.onAccent
|
||||
opacity: 0.0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
|
||||
if (pinArea.containsMouse) return;
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
appLauncherPanel.togglePin(modelData);
|
||||
return;
|
||||
}
|
||||
ripple.opacity = 0.18;
|
||||
rippleNumberAnimation.start();
|
||||
root.selectedIndex = index;
|
||||
root.activateSelected();
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: ripple.opacity = 0.18
|
||||
onReleased: ripple.opacity = 0.0
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: rippleNumberAnimation
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
to: 0.0
|
||||
duration: 320
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: index === appList.count - 1 ? 0 : 0.10
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: 28
|
||||
height: 28
|
||||
property bool iconLoaded: !modelData.isCalculator && iconImg.status === Image.Ready && iconImg.source !== "" && iconImg.status !== Image.Error
|
||||
Image {
|
||||
id: iconImg
|
||||
id: pinArea
|
||||
width: 28; height: 28
|
||||
z: 100
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
cache: false
|
||||
asynchronous: true
|
||||
source: modelData.isCalculator ? "qrc:/icons/calculate.svg" : Quickshell.iconPath(modelData.icon, "application-x-executable")
|
||||
visible: modelData.isCalculator || parent.iconLoaded
|
||||
preventStealing: true
|
||||
z: 100
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
propagateComposedEvents: false
|
||||
onClicked: {
|
||||
appLauncherPanel.togglePin(modelData);
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !modelData.isCalculator && !parent.iconLoaded
|
||||
text: "broken_image"
|
||||
text: "star"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeHeader
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 1
|
||||
Text {
|
||||
text: modelData.name
|
||||
color: (hovered || isSelected) ? Theme.onAccent : (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textPrimary)
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.bold: hovered || isSelected
|
||||
color: (parent.MouseArea.containsMouse || hovered || isSelected)
|
||||
? Theme.onAccent
|
||||
: (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textDisabled)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Text {
|
||||
text: modelData.isCalculator ? (modelData.expr + " = " + modelData.result) : (modelData.comment || modelData.genericName || "No description available")
|
||||
color: (hovered || isSelected) ? Theme.onAccent : (appLauncherPanel.isPinned(modelData) ? Theme.textSecondary : Theme.textSecondary)
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
font.italic: !(modelData.comment || modelData.genericName)
|
||||
opacity: (modelData.comment || modelData.genericName) ? 1.0 : 0.6
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Text {
|
||||
text: modelData.isCalculator ? "content_copy" : "chevron_right"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeBody
|
||||
color: (hovered || isSelected)
|
||||
? Theme.onAccent
|
||||
: (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textSecondary)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.rightMargin: 8 // Add margin to separate from star
|
||||
}
|
||||
// Add a spacing item between chevron and star
|
||||
Item { width: 8; height: 1 }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: ripple
|
||||
anchors.fill: parent
|
||||
color: Theme.onAccent
|
||||
opacity: 0.0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
// Prevent app launch if click is inside pinArea
|
||||
if (pinArea.containsMouse) return;
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
appLauncherPanel.togglePin(modelData);
|
||||
return;
|
||||
}
|
||||
ripple.opacity = 0.18;
|
||||
rippleNumberAnimation.start();
|
||||
root.selectedIndex = index;
|
||||
root.activateSelected();
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: ripple.opacity = 0.18
|
||||
onReleased: ripple.opacity = 0.0
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: rippleNumberAnimation
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
to: 0.0
|
||||
duration: 320
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: index === appList.count - 1 ? 0 : 0.10
|
||||
}
|
||||
// Pin/Unpin button (move to last child for stacking)
|
||||
Item {
|
||||
id: pinArea
|
||||
width: 28; height: 28
|
||||
z: 100 // Ensure above everything else
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
z: 100
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
propagateComposedEvents: false
|
||||
onClicked: {
|
||||
appLauncherPanel.togglePin(modelData);
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "star"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: (parent.MouseArea.containsMouse || hovered || isSelected)
|
||||
? Theme.onAccent
|
||||
: (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textDisabled)
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Corners {
|
||||
id: launcherCornerRight
|
||||
position: "bottomleft"
|
||||
size: 1.1
|
||||
fillColor: Theme.backgroundPrimary
|
||||
anchors.top: root.top
|
||||
offsetX: 416
|
||||
offsetY: 0
|
||||
}
|
||||
Corners {
|
||||
id: launcherCornerRight
|
||||
position: "bottomleft"
|
||||
size: 1.1
|
||||
fillColor: Theme.backgroundPrimary
|
||||
anchors.top: root.top
|
||||
offsetX: 416
|
||||
offsetY: 0
|
||||
}
|
||||
|
||||
Corners {
|
||||
id: launcherCornerLeft
|
||||
position: "bottomright"
|
||||
size: 1.1
|
||||
fillColor: Theme.backgroundPrimary
|
||||
anchors.top: root.top
|
||||
offsetX: -416
|
||||
offsetY: 0
|
||||
Corners {
|
||||
id: launcherCornerLeft
|
||||
position: "bottomright"
|
||||
size: 1.1
|
||||
fillColor: Theme.backgroundPrimary
|
||||
anchors.top: root.top
|
||||
offsetX: -416
|
||||
offsetY: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import Quickshell.Services.UPower
|
|||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Settings
|
||||
import "../../Helpers/Time.js" as Time
|
||||
|
||||
Item {
|
||||
id: batteryWidget
|
||||
|
|
@ -73,19 +74,40 @@ Item {
|
|||
}
|
||||
StyledTooltip {
|
||||
id: batteryTooltip
|
||||
positionAbove: false
|
||||
text: {
|
||||
let lines = [];
|
||||
if (batteryWidget.isReady) {
|
||||
if (!batteryWidget.isReady) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (batteryWidget.battery.timeToEmpty > 0) {
|
||||
lines.push("Time left: " + Time.formatVagueHumanReadableTime(batteryWidget.battery.timeToEmpty));
|
||||
}
|
||||
|
||||
if (batteryWidget.battery.timeToFull > 0) {
|
||||
lines.push("Time until full: " + Time.formatVagueHumanReadableTime(batteryWidget.battery.timeToFull));
|
||||
}
|
||||
|
||||
if (batteryWidget.battery.changeRate !== undefined) {
|
||||
const rate = batteryWidget.battery.changeRate;
|
||||
if (rate > 0) {
|
||||
lines.push(batteryWidget.charging ? "Charging rate: " + rate.toFixed(2) + " W" : "Discharging rate: " + rate.toFixed(2) + " W");
|
||||
}
|
||||
else if (rate < 0) {
|
||||
lines.push("Discharging rate: " + Math.abs(rate).toFixed(2) + " W");
|
||||
}
|
||||
else {
|
||||
lines.push("Estimating...");
|
||||
}
|
||||
}
|
||||
else {
|
||||
lines.push(batteryWidget.charging ? "Charging" : "Discharging");
|
||||
lines.push(Math.round(batteryWidget.percent) + "%");
|
||||
if (batteryWidget.battery.changeRate !== undefined)
|
||||
lines.push("Rate: " + batteryWidget.battery.changeRate.toFixed(2) + " W");
|
||||
if (batteryWidget.battery.timeToEmpty > 0)
|
||||
lines.push("Time left: " + Math.floor(batteryWidget.battery.timeToEmpty / 60) + " min");
|
||||
if (batteryWidget.battery.timeToFull > 0)
|
||||
lines.push("Time to full: " + Math.floor(batteryWidget.battery.timeToFull / 60) + " min");
|
||||
if (batteryWidget.battery.healthPercentage !== undefined)
|
||||
lines.push("Health: " + Math.round(batteryWidget.battery.healthPercentage) + "%");
|
||||
}
|
||||
|
||||
|
||||
if (batteryWidget.battery.healthPercentage !== undefined && batteryWidget.battery.healthPercentage > 0) {
|
||||
lines.push("Health: " + Math.round(batteryWidget.battery.healthPercentage) + "%");
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
|
|
|||
272
Bar/Modules/Bluetooth.qml
Normal file
272
Bar/Modules/Bluetooth.qml
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: Settings.settings.bluetoothEnabled ? 22 : 0
|
||||
height: Settings.settings.bluetoothEnabled ? 22 : 0
|
||||
|
||||
property bool menuVisible: false
|
||||
|
||||
// Bluetooth icon/button
|
||||
Item {
|
||||
id: bluetoothIcon
|
||||
width: 22; height: 22
|
||||
visible: Settings.settings.bluetoothEnabled
|
||||
|
||||
// Check if any devices are currently connected
|
||||
property bool hasConnectedDevices: {
|
||||
if (!Bluetooth.defaultAdapter) return false;
|
||||
|
||||
for (let i = 0; i < Bluetooth.defaultAdapter.devices.count; i++) {
|
||||
if (Bluetooth.defaultAdapter.devices.valueAt(i).connected) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Text {
|
||||
id: bluetoothText
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!Bluetooth.defaultAdapter || !Bluetooth.defaultAdapter.enabled) {
|
||||
return "bluetooth_disabled"
|
||||
} else if (parent.hasConnectedDevices) {
|
||||
return "bluetooth_connected"
|
||||
} else {
|
||||
return "bluetooth"
|
||||
}
|
||||
}
|
||||
font.family: mouseAreaBluetooth.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: mouseAreaBluetooth.containsMouse ? Theme.accentPrimary : Theme.textPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaBluetooth
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
bluetoothMenu.visible = !bluetoothMenu.visible;
|
||||
// Enable adapter and start discovery when menu opens
|
||||
if (bluetoothMenu.visible && Bluetooth.defaultAdapter) {
|
||||
if (!Bluetooth.defaultAdapter.enabled) {
|
||||
Bluetooth.defaultAdapter.enabled = true;
|
||||
}
|
||||
if (!Bluetooth.defaultAdapter.discovering) {
|
||||
Bluetooth.defaultAdapter.discovering = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: bluetoothTooltip.tooltipVisible = true
|
||||
onExited: bluetoothTooltip.tooltipVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
StyledTooltip {
|
||||
id: bluetoothTooltip
|
||||
text: "Bluetooth Devices"
|
||||
positionAbove: false
|
||||
tooltipVisible: false
|
||||
targetItem: bluetoothIcon
|
||||
delay: 200
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: bluetoothMenu
|
||||
implicitWidth: 320
|
||||
implicitHeight: 480
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
onVisibleChanged: {
|
||||
// Stop discovery when menu closes to save battery
|
||||
if (!visible && Bluetooth.defaultAdapter && Bluetooth.defaultAdapter.discovering) {
|
||||
Bluetooth.defaultAdapter.discovering = false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 12
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
text: "bluetooth"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Bluetooth Devices"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "close"
|
||||
onClicked: {
|
||||
bluetoothMenu.visible = false;
|
||||
if (Bluetooth.defaultAdapter && Bluetooth.defaultAdapter.discovering) {
|
||||
Bluetooth.defaultAdapter.discovering = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.12
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: deviceList
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.devices : []
|
||||
spacing: 8
|
||||
clip: true
|
||||
|
||||
delegate: Item {
|
||||
width: parent.width
|
||||
height: 48
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 8
|
||||
color: modelData.connected ? Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.44) : (deviceMouseArea.containsMouse ? Theme.highlight : "transparent")
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: modelData.connected ? "bluetooth" : "bluetooth_disabled"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 18
|
||||
color: deviceMouseArea.containsMouse ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textSecondary)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: {
|
||||
let deviceName = modelData.name || modelData.deviceName || "Unknown Device";
|
||||
// Hide MAC addresses and show "Unknown Device" instead
|
||||
let macPattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
||||
if (macPattern.test(deviceName)) {
|
||||
return "Unknown Device";
|
||||
}
|
||||
return deviceName;
|
||||
}
|
||||
color: deviceMouseArea.containsMouse ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textPrimary)
|
||||
font.pixelSize: 14
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: {
|
||||
let deviceName = modelData.name || modelData.deviceName || "";
|
||||
let macPattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
||||
if (macPattern.test(deviceName)) {
|
||||
// Show MAC address in subtitle for unnamed devices
|
||||
return modelData.address + " • " + (modelData.paired ? "Paired" : "Available");
|
||||
} else {
|
||||
// Show only status for named devices
|
||||
return modelData.paired ? "Paired" : "Available";
|
||||
}
|
||||
}
|
||||
color: deviceMouseArea.containsMouse ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textSecondary)
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 22
|
||||
Layout.preferredHeight: 22
|
||||
visible: modelData.pairing || modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting
|
||||
|
||||
Spinner {
|
||||
visible: parent.visible
|
||||
running: parent.visible
|
||||
color: Theme.accentPrimary
|
||||
anchors.centerIn: parent
|
||||
size: 22
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
// Handle device actions: disconnect, pair, or connect
|
||||
if (modelData.connected) {
|
||||
modelData.disconnect();
|
||||
} else if (!modelData.paired) {
|
||||
modelData.pair();
|
||||
} else {
|
||||
modelData.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Discovering indicator
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
visible: Bluetooth.defaultAdapter && Bluetooth.defaultAdapter.discovering
|
||||
|
||||
Text {
|
||||
text: "Scanning for devices..."
|
||||
font.pixelSize: 12
|
||||
color: Theme.textSecondary
|
||||
}
|
||||
|
||||
Spinner {
|
||||
running: true
|
||||
color: Theme.accentPrimary
|
||||
size: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +128,7 @@ Item {
|
|||
StyledTooltip {
|
||||
id: brightnessTooltip
|
||||
text: "Brightness: " + brightness + "%"
|
||||
positionAbove: false
|
||||
tooltipVisible: false
|
||||
targetItem: pill
|
||||
delay: 1500
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ Rectangle {
|
|||
StyledTooltip {
|
||||
id: dateTooltip
|
||||
text: Time.dateString
|
||||
positionAbove: false
|
||||
tooltipVisible: showTooltip && !calendar.visible
|
||||
targetItem: clockWidget
|
||||
delay: 200
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import Quickshell
|
||||
import qs.Settings
|
||||
|
||||
|
|
@ -21,120 +21,456 @@ PopupWindow {
|
|||
anchor.rect.x: anchorX
|
||||
anchor.rect.y: anchorY - 4
|
||||
|
||||
// Recursive function to destroy all open submenus in delegate tree, safely avoiding infinite recursion
|
||||
function destroySubmenusRecursively(item) {
|
||||
if (!item || !item.contentItem) return;
|
||||
var children = item.contentItem.children;
|
||||
for (var i = 0; i < children.length; ++i) {
|
||||
var child = children[i];
|
||||
if (child.subMenu) {
|
||||
child.subMenu.hideMenu();
|
||||
child.subMenu.destroy();
|
||||
child.subMenu = null;
|
||||
}
|
||||
// Recursively destroy submenus only if the child has contentItem to prevent issues
|
||||
if (child.contentItem) {
|
||||
destroySubmenusRecursively(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showAt(item, x, y) {
|
||||
if (!item) {
|
||||
console.warn("CustomTrayMenu: anchorItem is undefined, not showing menu.");
|
||||
console.warn("CustomTrayMenu: anchorItem is undefined, won't show menu.");
|
||||
return;
|
||||
}
|
||||
anchorItem = item
|
||||
anchorX = x
|
||||
anchorY = y
|
||||
visible = true
|
||||
forceActiveFocus()
|
||||
Qt.callLater(() => trayMenu.anchor.updateAnchor())
|
||||
anchorItem = item;
|
||||
anchorX = x;
|
||||
anchorY = y;
|
||||
visible = true;
|
||||
forceActiveFocus();
|
||||
Qt.callLater(() => trayMenu.anchor.updateAnchor());
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
visible = false
|
||||
visible = false;
|
||||
destroySubmenusRecursively(listView);
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
Keys.onEscapePressed: trayMenu.hideMenu()
|
||||
anchors.fill: parent;
|
||||
Keys.onEscapePressed: trayMenu.hideMenu();
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: opener
|
||||
menu: trayMenu.menu
|
||||
id: opener;
|
||||
menu: trayMenu.menu;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bg
|
||||
anchors.fill: parent
|
||||
color: Theme.surfaceVariant || "#222"
|
||||
border.color: Theme.outline || "#444"
|
||||
border.width: 1
|
||||
radius: 12
|
||||
z: 0
|
||||
id: bg;
|
||||
anchors.fill: parent;
|
||||
color: Theme.backgroundPrimary || "#222";
|
||||
border.color: Theme.outline || "#444";
|
||||
border.width: 1;
|
||||
radius: 12;
|
||||
z: 0;
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
spacing: 2
|
||||
interactive: false
|
||||
enabled: trayMenu.visible
|
||||
clip: true
|
||||
id: listView;
|
||||
anchors.fill: parent;
|
||||
anchors.margins: 6;
|
||||
spacing: 2;
|
||||
interactive: false;
|
||||
enabled: trayMenu.visible;
|
||||
clip: true;
|
||||
|
||||
model: ScriptModel {
|
||||
values: opener.children ? [...opener.children.values] : []
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: entry
|
||||
required property var modelData
|
||||
id: entry;
|
||||
required property var modelData;
|
||||
|
||||
width: listView.width
|
||||
height: (modelData?.isSeparator) ? 8 : 32
|
||||
color: "transparent"
|
||||
radius: 12
|
||||
width: listView.width;
|
||||
height: (modelData?.isSeparator) ? 8 : 32;
|
||||
color: "transparent";
|
||||
radius: 12;
|
||||
|
||||
property var subMenu: null;
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 20
|
||||
height: 1
|
||||
color: Qt.darker(Theme.surfaceVariant || "#222", 1.4)
|
||||
visible: modelData?.isSeparator ?? false
|
||||
anchors.centerIn: parent;
|
||||
width: parent.width - 20;
|
||||
height: 1;
|
||||
color: Qt.darker(Theme.backgroundPrimary || "#222", 1.4);
|
||||
visible: modelData?.isSeparator ?? false;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bg
|
||||
anchors.fill: parent
|
||||
color: mouseArea.containsMouse ? Theme.highlight : "transparent"
|
||||
radius: 8
|
||||
visible: !(modelData?.isSeparator ?? false)
|
||||
property color hoverTextColor: mouseArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||
id: bg;
|
||||
anchors.fill: parent;
|
||||
color: mouseArea.containsMouse ? Theme.highlight : "transparent";
|
||||
radius: 8;
|
||||
visible: !(modelData?.isSeparator ?? false);
|
||||
property color hoverTextColor: mouseArea.containsMouse ? Theme.onAccent : Theme.textPrimary;
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
spacing: 8
|
||||
anchors.fill: parent;
|
||||
anchors.leftMargin: 12;
|
||||
anchors.rightMargin: 12;
|
||||
spacing: 8;
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: (modelData?.enabled ?? true) ? bg.hoverTextColor : Theme.textDisabled
|
||||
text: modelData?.text ?? ""
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true;
|
||||
color: (modelData?.enabled ?? true) ? bg.hoverTextColor : Theme.textDisabled;
|
||||
text: modelData?.text ?? "";
|
||||
font.family: Theme.fontFamily;
|
||||
font.pixelSize: Theme.fontSizeSmall;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
elide: Text.ElideRight;
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: 16
|
||||
Layout.preferredHeight: 16
|
||||
source: modelData?.icon ?? ""
|
||||
visible: (modelData?.icon ?? "") !== ""
|
||||
fillMode: Image.PreserveAspectFit
|
||||
Layout.preferredWidth: 16;
|
||||
Layout.preferredHeight: 16;
|
||||
source: modelData?.icon ?? "";
|
||||
visible: (modelData?.icon ?? "") !== "";
|
||||
fillMode: Image.PreserveAspectFit;
|
||||
}
|
||||
|
||||
Text {
|
||||
// Material Symbols Outlined chevron right for submenu
|
||||
text: modelData?.hasChildren ? "menu" : "";
|
||||
font.family: "Material Symbols Outlined";
|
||||
font.pixelSize: 18;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
visible: modelData?.hasChildren ?? false;
|
||||
color: Theme.textPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && trayMenu.visible
|
||||
id: mouseArea;
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && trayMenu.visible;
|
||||
|
||||
onClicked: {
|
||||
if (modelData && !modelData.isSeparator) {
|
||||
modelData.triggered()
|
||||
trayMenu.hideMenu()
|
||||
if (modelData.hasChildren) {
|
||||
// Submenus open on hover; ignore click here
|
||||
return;
|
||||
}
|
||||
modelData.triggered();
|
||||
trayMenu.hideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
if (!trayMenu.visible) return;
|
||||
|
||||
if (modelData?.hasChildren) {
|
||||
// Close sibling submenus immediately
|
||||
for (let i = 0; i < listView.contentItem.children.length; i++) {
|
||||
const sibling = listView.contentItem.children[i];
|
||||
if (sibling !== entry && sibling.subMenu) {
|
||||
sibling.subMenu.hideMenu();
|
||||
sibling.subMenu.destroy();
|
||||
sibling.subMenu = null;
|
||||
}
|
||||
}
|
||||
if (entry.subMenu) {
|
||||
entry.subMenu.hideMenu();
|
||||
entry.subMenu.destroy();
|
||||
entry.subMenu = null;
|
||||
}
|
||||
var globalPos = entry.mapToGlobal(0, 0);
|
||||
var submenuWidth = 180;
|
||||
var gap = 12;
|
||||
var openLeft = (globalPos.x + entry.width + submenuWidth > Screen.width);
|
||||
var anchorX = openLeft ? -submenuWidth - gap : entry.width + gap;
|
||||
|
||||
entry.subMenu = subMenuComponent.createObject(trayMenu, {
|
||||
menu: modelData,
|
||||
anchorItem: entry,
|
||||
anchorX: anchorX,
|
||||
anchorY: 0
|
||||
});
|
||||
entry.subMenu.showAt(entry, anchorX, 0);
|
||||
} else {
|
||||
// Hovered item without submenu; close siblings
|
||||
for (let i = 0; i < listView.contentItem.children.length; i++) {
|
||||
const sibling = listView.contentItem.children[i];
|
||||
if (sibling.subMenu) {
|
||||
sibling.subMenu.hideMenu();
|
||||
sibling.subMenu.destroy();
|
||||
sibling.subMenu = null;
|
||||
}
|
||||
}
|
||||
if (entry.subMenu) {
|
||||
entry.subMenu.hideMenu();
|
||||
entry.subMenu.destroy();
|
||||
entry.subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
if (entry.subMenu && !entry.subMenu.containsMouse()) {
|
||||
entry.subMenu.hideMenu();
|
||||
entry.subMenu.destroy();
|
||||
entry.subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified containsMouse without recursive calls to avoid stack overflow
|
||||
function containsMouse() {
|
||||
return mouseArea.containsMouse;
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (subMenu) {
|
||||
subMenu.destroy();
|
||||
subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: subMenuComponent;
|
||||
|
||||
PopupWindow {
|
||||
id: subMenu;
|
||||
implicitWidth: 180;
|
||||
implicitHeight: Math.max(40, listView.contentHeight + 12);
|
||||
visible: false;
|
||||
color: "transparent";
|
||||
|
||||
property QsMenuHandle menu;
|
||||
property var anchorItem: null;
|
||||
property real anchorX;
|
||||
property real anchorY;
|
||||
|
||||
anchor.item: anchorItem ? anchorItem : null;
|
||||
anchor.rect.x: anchorX;
|
||||
anchor.rect.y: anchorY;
|
||||
|
||||
function showAt(item, x, y) {
|
||||
if (!item) {
|
||||
console.warn("subMenuComponent: anchorItem is undefined, not showing menu.");
|
||||
return;
|
||||
}
|
||||
anchorItem = item;
|
||||
anchorX = x;
|
||||
anchorY = y;
|
||||
visible = true;
|
||||
Qt.callLater(() => subMenu.anchor.updateAnchor());
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
visible = false;
|
||||
// Close all submenus recursively in this submenu
|
||||
for (let i = 0; i < listView.contentItem.children.length; i++) {
|
||||
const child = listView.contentItem.children[i];
|
||||
if (child.subMenu) {
|
||||
child.subMenu.hideMenu();
|
||||
child.subMenu.destroy();
|
||||
child.subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified containsMouse avoiding recursive calls
|
||||
function containsMouse() {
|
||||
return subMenu.containsMouse;
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent;
|
||||
Keys.onEscapePressed: subMenu.hideMenu();
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: opener;
|
||||
menu: subMenu.menu;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bg;
|
||||
anchors.fill: parent;
|
||||
color: Theme.backgroundPrimary || "#222";
|
||||
border.color: Theme.outline || "#444";
|
||||
border.width: 1;
|
||||
radius: 12;
|
||||
z: 0;
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView;
|
||||
anchors.fill: parent;
|
||||
anchors.margins: 6;
|
||||
spacing: 2;
|
||||
interactive: false;
|
||||
enabled: subMenu.visible;
|
||||
clip: true;
|
||||
|
||||
model: ScriptModel {
|
||||
values: opener.children ? [...opener.children.values] : [];
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: entry;
|
||||
required property var modelData;
|
||||
|
||||
width: listView.width;
|
||||
height: (modelData?.isSeparator) ? 8 : 32;
|
||||
color: "transparent";
|
||||
radius: 12;
|
||||
|
||||
property var subMenu: null;
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent;
|
||||
width: parent.width - 20;
|
||||
height: 1;
|
||||
color: Qt.darker(Theme.surfaceVariant || "#222", 1.4);
|
||||
visible: modelData?.isSeparator ?? false;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bg;
|
||||
anchors.fill: parent;
|
||||
color: mouseArea.containsMouse ? Theme.highlight : "transparent";
|
||||
radius: 8;
|
||||
visible: !(modelData?.isSeparator ?? false);
|
||||
property color hoverTextColor: mouseArea.containsMouse ? Theme.onAccent : Theme.textPrimary;
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent;
|
||||
anchors.leftMargin: 12;
|
||||
anchors.rightMargin: 12;
|
||||
spacing: 8;
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true;
|
||||
color: (modelData?.enabled ?? true) ? bg.hoverTextColor : Theme.textDisabled;
|
||||
text: modelData?.text ?? "";
|
||||
font.family: Theme.fontFamily;
|
||||
font.pixelSize: Theme.fontSizeSmall;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
elide: Text.ElideRight;
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: 16;
|
||||
Layout.preferredHeight: 16;
|
||||
source: modelData?.icon ?? "";
|
||||
visible: (modelData?.icon ?? "") !== "";
|
||||
fillMode: Image.PreserveAspectFit;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData?.hasChildren ? "\uE5CC" : "";
|
||||
font.family: "Material Symbols Outlined";
|
||||
font.pixelSize: 18;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
visible: modelData?.hasChildren ?? false;
|
||||
color: Theme.textPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea;
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
enabled: (modelData?.enabled ?? true) && !(modelData?.isSeparator ?? false) && subMenu.visible;
|
||||
|
||||
onClicked: {
|
||||
if (modelData && !modelData.isSeparator) {
|
||||
if (modelData.hasChildren) {
|
||||
return;
|
||||
}
|
||||
modelData.triggered();
|
||||
trayMenu.hideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
if (!subMenu.visible) return;
|
||||
|
||||
if (modelData?.hasChildren) {
|
||||
for (let i = 0; i < listView.contentItem.children.length; i++) {
|
||||
const sibling = listView.contentItem.children[i];
|
||||
if (sibling !== entry && sibling.subMenu) {
|
||||
sibling.subMenu.hideMenu();
|
||||
sibling.subMenu.destroy();
|
||||
sibling.subMenu = null;
|
||||
}
|
||||
}
|
||||
if (entry.subMenu) {
|
||||
entry.subMenu.hideMenu();
|
||||
entry.subMenu.destroy();
|
||||
entry.subMenu = null;
|
||||
}
|
||||
var globalPos = entry.mapToGlobal(0, 0);
|
||||
var submenuWidth = 180;
|
||||
var gap = 12;
|
||||
var openLeft = (globalPos.x + entry.width + submenuWidth > Screen.width);
|
||||
var anchorX = openLeft ? -submenuWidth - gap : entry.width + gap;
|
||||
|
||||
entry.subMenu = subMenuComponent.createObject(subMenu, {
|
||||
menu: modelData,
|
||||
anchorItem: entry,
|
||||
anchorX: anchorX,
|
||||
anchorY: 0
|
||||
});
|
||||
entry.subMenu.showAt(entry, anchorX, 0);
|
||||
} else {
|
||||
for (let i = 0; i < listView.contentItem.children.length; i++) {
|
||||
const sibling = listView.contentItem.children[i];
|
||||
if (sibling.subMenu) {
|
||||
sibling.subMenu.hideMenu();
|
||||
sibling.subMenu.destroy();
|
||||
sibling.subMenu = null;
|
||||
}
|
||||
}
|
||||
if (entry.subMenu) {
|
||||
entry.subMenu.hideMenu();
|
||||
entry.subMenu.destroy();
|
||||
entry.subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
if (entry.subMenu && !entry.subMenu.containsMouse()) {
|
||||
entry.subMenu.hideMenu();
|
||||
entry.subMenu.destroy();
|
||||
entry.subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified & safe containsMouse avoiding recursion
|
||||
function containsMouse() {
|
||||
return mouseArea.containsMouse;
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (subMenu) {
|
||||
subMenu.destroy();
|
||||
subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell.Widgets
|
||||
import QtQuick.Effects
|
||||
import qs.Settings
|
||||
import qs.Services
|
||||
import qs.Components
|
||||
|
|
@ -54,17 +55,16 @@ Item {
|
|||
anchors.margins: 1
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
cache: false
|
||||
asynchronous: true
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
source: MusicManager.trackArtUrl
|
||||
visible: source.toString() !== ""
|
||||
|
||||
// Rounded corners using layer
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
cached: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskSource: Rectangle {
|
||||
width: albumArt.width
|
||||
height: albumArt.height
|
||||
|
|
|
|||
80
Bar/Modules/SettingsButton.qml
Normal file
80
Bar/Modules/SettingsButton.qml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
import qs.Widgets.SettingsWindow
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 22
|
||||
height: 22
|
||||
|
||||
property var settingsWindow: null
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
radius: width / 2
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "settings"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: mouseArea.containsMouse ? Theme.accentPrimary : Theme.textPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (!settingsWindow) {
|
||||
// Create new window
|
||||
settingsWindow = settingsComponent.createObject(null); // No parent to avoid dependency issues
|
||||
if (settingsWindow) {
|
||||
settingsWindow.visible = true;
|
||||
// Handle window closure
|
||||
settingsWindow.visibleChanged.connect(function() {
|
||||
if (settingsWindow && !settingsWindow.visible) {
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (settingsWindow.visible) {
|
||||
// Close and destroy window
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.visible = false;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledTooltip {
|
||||
text: "Settings"
|
||||
targetItem: mouseArea
|
||||
tooltipVisible: mouseArea.containsMouse
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: settingsComponent
|
||||
SettingsWindow {}
|
||||
}
|
||||
|
||||
// Clean up on destruction
|
||||
Component.onDestruction: {
|
||||
if (settingsWindow) {
|
||||
var windowToDestroy = settingsWindow;
|
||||
settingsWindow = null;
|
||||
windowToDestroy.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ Row {
|
|||
spacing: 10
|
||||
visible: Settings.settings.showSystemInfoInBar
|
||||
|
||||
width: Math.floor(cpuUsageLayout.width + cpuTempLayout.width + memoryUsageLayout.width + (2 * 10))
|
||||
|
||||
Row {
|
||||
id: cpuUsageLayout
|
||||
spacing: 6
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import QtQuick
|
|||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Effects
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
import qs.Settings
|
||||
|
|
@ -14,10 +14,10 @@ Row {
|
|||
property var trayMenu
|
||||
spacing: 8
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
|
||||
property bool containsMouse: false
|
||||
property var systemTray: SystemTray
|
||||
|
||||
|
||||
Repeater {
|
||||
model: systemTray.items
|
||||
delegate: Item {
|
||||
|
|
@ -26,7 +26,7 @@ Row {
|
|||
// Hide Spotify icon, or adjust to your liking
|
||||
visible: modelData && modelData.id !== "spotify"
|
||||
property bool isHovered: trayMouseArea.containsMouse
|
||||
|
||||
|
||||
// Hover scale animation
|
||||
scale: isHovered ? 1.15 : 1.0
|
||||
Behavior on scale {
|
||||
|
|
@ -35,7 +35,7 @@ Row {
|
|||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Subtle rotation on hover
|
||||
rotation: isHovered ? 5 : 0
|
||||
Behavior on rotation {
|
||||
|
|
@ -44,7 +44,7 @@ Row {
|
|||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 16
|
||||
|
|
@ -63,7 +63,8 @@ Row {
|
|||
backer.fillMode: Image.PreserveAspectFit
|
||||
source: {
|
||||
let icon = modelData?.icon || "";
|
||||
if (!icon) return "";
|
||||
if (!icon)
|
||||
return "";
|
||||
// Process icon path
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
|
|
@ -80,72 +81,71 @@ Row {
|
|||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
|
||||
}
|
||||
Component.onCompleted: {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: trayMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: (mouse) => {
|
||||
if (!modelData) return;
|
||||
|
||||
onClicked: mouse => {
|
||||
if (!modelData)
|
||||
return;
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Close any open menu first
|
||||
if (trayMenu && trayMenu.visible) {
|
||||
trayMenu.hideMenu()
|
||||
trayMenu.hideMenu();
|
||||
}
|
||||
|
||||
|
||||
if (!modelData.onlyMenu) {
|
||||
modelData.activate()
|
||||
modelData.activate();
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
// Close any open menu first
|
||||
if (trayMenu && trayMenu.visible) {
|
||||
trayMenu.hideMenu()
|
||||
trayMenu.hideMenu();
|
||||
}
|
||||
|
||||
modelData.secondaryActivate && modelData.secondaryActivate()
|
||||
|
||||
modelData.secondaryActivate && modelData.secondaryActivate();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
trayTooltip.tooltipVisible = false
|
||||
console.log("Right click on", modelData.id, "hasMenu:", modelData.hasMenu, "menu:", modelData.menu)
|
||||
trayTooltip.tooltipVisible = false;
|
||||
// If menu is already visible, close it
|
||||
if (trayMenu && trayMenu.visible) {
|
||||
trayMenu.hideMenu()
|
||||
return
|
||||
trayMenu.hideMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (modelData.hasMenu && modelData.menu && trayMenu) {
|
||||
// Anchor the menu to the tray icon item (parent) and position it below the icon
|
||||
const menuX = (width / 2) - (trayMenu.width / 2);
|
||||
const menuY = height + 20;
|
||||
trayMenu.menu = modelData.menu;
|
||||
trayMenu.showAt(parent, menuX, menuY);
|
||||
} else {
|
||||
// console.log("No menu available for", modelData.id, "or trayMenu not set")
|
||||
}
|
||||
} else
|
||||
// console.log("No menu available for", modelData.id, "or trayMenu not set")
|
||||
{}
|
||||
}
|
||||
}
|
||||
onEntered: trayTooltip.tooltipVisible = true
|
||||
onExited: trayTooltip.tooltipVisible = false
|
||||
}
|
||||
|
||||
|
||||
StyledTooltip {
|
||||
id: trayTooltip
|
||||
text: modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item"
|
||||
positionAbove: false
|
||||
tooltipVisible: false
|
||||
targetItem: trayIcon
|
||||
delay: 200
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// No cache cleanup needed
|
||||
}
|
||||
|
||||
Component.onDestruction:
|
||||
// No cache cleanup needed
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Item {
|
|||
// Attach custom tooltip
|
||||
StyledTooltip {
|
||||
id: styledTooltip
|
||||
positionAbove: false
|
||||
}
|
||||
|
||||
function getAppIcon(toplevel: Toplevel): string {
|
||||
|
|
@ -74,7 +75,6 @@ Item {
|
|||
height: Math.max(12, Settings.settings.taskbarIconSize * 0.625)
|
||||
anchors.centerIn: parent
|
||||
source: getAppIcon(modelData)
|
||||
smooth: true
|
||||
visible: source.toString() !== ""
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,8 @@ Item {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onEntered: {
|
||||
styledTooltip.text = appTitle || appId;
|
||||
var text = appTitle || appId;
|
||||
styledTooltip.text = text.length > 60 ? text.substring(0, 60) + "..." : text;
|
||||
styledTooltip.targetItem = appButton;
|
||||
styledTooltip.tooltipVisible = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ Item {
|
|||
|
||||
StyledTooltip {
|
||||
id: volumeTooltip
|
||||
text: "Volume: " + volume + "%\nScroll up/down to change volume.\nLeft click to open the input/output selection."
|
||||
text: "Volume: " + volume + "%\nLeft click for advanced settings.\nScroll up/down to change volume."
|
||||
positionAbove: false
|
||||
tooltipVisible: !ioSelector.visible && volumeDisplay.containsMouse
|
||||
targetItem: pillIndicator
|
||||
delay: 1500
|
||||
|
|
|
|||
370
Bar/Modules/Wifi.qml
Normal file
370
Bar/Modules/Wifi.qml
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: Settings.settings.wifiEnabled ? 22 : 0
|
||||
height: Settings.settings.wifiEnabled ? 22 : 0
|
||||
|
||||
property bool menuVisible: false
|
||||
property string passwordPromptSsid: ""
|
||||
property string passwordInput: ""
|
||||
property bool showPasswordPrompt: false
|
||||
|
||||
Network {
|
||||
id: network
|
||||
}
|
||||
|
||||
// WiFi icon/button
|
||||
Item {
|
||||
id: wifiIcon
|
||||
width: 22; height: 22
|
||||
visible: Settings.settings.wifiEnabled
|
||||
|
||||
property int currentSignal: {
|
||||
let maxSignal = 0;
|
||||
for (const net in network.networks) {
|
||||
if (network.networks[net].connected && network.networks[net].signal > maxSignal) {
|
||||
maxSignal = network.networks[net].signal;
|
||||
}
|
||||
}
|
||||
return maxSignal;
|
||||
}
|
||||
|
||||
Text {
|
||||
id: wifiText
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
let connected = false;
|
||||
for (const net in network.networks) {
|
||||
if (network.networks[net].connected) {
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return connected ? network.signalIcon(parent.currentSignal) : "wifi_off"
|
||||
}
|
||||
font.family: mouseAreaWifi.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: mouseAreaWifi.containsMouse ? Theme.accentPrimary : Theme.textPrimary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaWifi
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
wifiMenu.visible = !wifiMenu.visible;
|
||||
if (wifiMenu.visible) {
|
||||
network.onMenuOpened();
|
||||
} else {
|
||||
network.onMenuClosed();
|
||||
}
|
||||
}
|
||||
onEntered: wifiTooltip.tooltipVisible = true
|
||||
onExited: wifiTooltip.tooltipVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
StyledTooltip {
|
||||
id: wifiTooltip
|
||||
text: "WiFi Networks"
|
||||
positionAbove: false
|
||||
tooltipVisible: false
|
||||
targetItem: wifiIcon
|
||||
delay: 200
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: wifiMenu
|
||||
implicitWidth: 320
|
||||
implicitHeight: 480
|
||||
visible: false
|
||||
color: "transparent"
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.right: 0
|
||||
margins.top: 0
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 12
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
text: "wifi"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
color: Theme.accentPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "WiFi Networks"
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "refresh"
|
||||
onClicked: network.refreshNetworks()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "close"
|
||||
onClicked: {
|
||||
wifiMenu.visible = false;
|
||||
network.onMenuClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.12
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: networkList
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: Object.values(network.networks)
|
||||
spacing: 8
|
||||
clip: true
|
||||
|
||||
delegate: Item {
|
||||
width: parent.width
|
||||
height: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 108 : 48 // 48 for network + 60 for password prompt
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 48
|
||||
radius: 8
|
||||
color: modelData.connected ? Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.44) : (networkMouseArea.containsMouse ? Theme.highlight : "transparent")
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: network.signalIcon(modelData.signal)
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 18
|
||||
color: networkMouseArea.containsMouse ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textSecondary)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: modelData.ssid || "Unknown Network"
|
||||
color: networkMouseArea.containsMouse ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textPrimary)
|
||||
font.pixelSize: 14
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: modelData.security && modelData.security !== "--" ? modelData.security : "Open"
|
||||
color: networkMouseArea.containsMouse ? Theme.backgroundPrimary : (modelData.connected ? Theme.accentPrimary : Theme.textSecondary)
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: network.connectStatusSsid === modelData.ssid && network.connectStatus === "error" && network.connectError.length > 0
|
||||
text: network.connectError
|
||||
color: Theme.error
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 22
|
||||
Layout.preferredHeight: 22
|
||||
visible: network.connectStatusSsid === modelData.ssid && (network.connectStatus !== "" || network.connectingSsid === modelData.ssid)
|
||||
|
||||
Spinner {
|
||||
visible: network.connectingSsid === modelData.ssid
|
||||
running: network.connectingSsid === modelData.ssid
|
||||
color: Theme.accentPrimary
|
||||
anchors.centerIn: parent
|
||||
size: 22
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: network.connectStatus === "success" && !network.connectingSsid
|
||||
text: "check_circle"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 18
|
||||
color: "#43a047"
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: network.connectStatus === "error" && !network.connectingSsid
|
||||
text: "error"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 18
|
||||
color: Theme.error
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: modelData.connected
|
||||
text: "connected"
|
||||
color: networkMouseArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: networkMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
network.disconnectNetwork(modelData.ssid);
|
||||
} else if (network.isSecured(modelData.security) && !modelData.existing) {
|
||||
passwordPromptSsid = modelData.ssid;
|
||||
showPasswordPrompt = true;
|
||||
passwordInput = ""; // Clear previous input
|
||||
Qt.callLater(function() {
|
||||
passwordInputField.forceActiveFocus();
|
||||
});
|
||||
} else {
|
||||
network.connectNetwork(modelData.ssid, modelData.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password prompt section
|
||||
Rectangle {
|
||||
id: passwordPromptSection
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0
|
||||
Layout.margins: 8
|
||||
visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt
|
||||
color: Theme.surfaceVariant
|
||||
radius: 8
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 10
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
border.color: passwordInputField.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
|
||||
TextInput {
|
||||
id: passwordInputField
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
text: passwordInput
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
focus: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhNone
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: passwordInput = text
|
||||
onAccepted: {
|
||||
network.submitPassword(passwordPromptSsid, passwordInput);
|
||||
showPasswordPrompt = false;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: passwordInputMouseArea
|
||||
anchors.fill: parent
|
||||
onClicked: passwordInputField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 36
|
||||
radius: 18
|
||||
color: Theme.accentPrimary
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 0
|
||||
opacity: 1.0
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
network.submitPassword(passwordPromptSsid, passwordInput);
|
||||
showPasswordPrompt = false;
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onEntered: parent.color = Qt.darker(Theme.accentPrimary, 1.1)
|
||||
onExited: parent.color = Theme.accentPrimary
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Connect"
|
||||
color: Theme.backgroundPrimary
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import QtQuick
|
|||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Settings
|
||||
|
|
@ -124,13 +124,13 @@ Item {
|
|||
border.color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.1)
|
||||
border.width: 1
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
color: "black"
|
||||
radius: 12
|
||||
samples: 24
|
||||
verticalOffset: 0
|
||||
horizontalOffset: 0
|
||||
opacity: 0.10
|
||||
layer.effect: MultiEffect {
|
||||
shadowColor: "black"
|
||||
// radius: 12
|
||||
|
||||
shadowVerticalOffset: 0
|
||||
shadowHorizontalOffset: 0
|
||||
shadowOpacity: 0.10
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue