diff --git a/AudioPopup.qml b/AudioPopup.qml new file mode 100644 index 0000000..c99d022 --- /dev/null +++ b/AudioPopup.qml @@ -0,0 +1,82 @@ +import QtQuick 2.15 +import Quickshell +import Quickshell.Services.Pipewire + +PopupWindow { + id: audioPopup + + required property var bar + + anchor.window: bar + anchor.rect.x: bar.width - 300 + anchor.rect.y: bar.implicitHeight + implicitWidth: 300 + implicitHeight: audioSinks.length > 0 ? Math.min(audioSinks.length * 48 + 8, 400) : 52 + grabFocus: true + color: "black" + + property var audioSinks: Pipewire.nodes.values.filter(n => !n.isStream && n.isSink && n.audio !== null) + + PwObjectTracker { + objects: Pipewire.defaultAudioSink ? [Pipewire.defaultAudioSink] : [] + } + + Text { + anchors.centerIn: parent + visible: audioPopup.audioSinks.length === 0 + text: "No audio devices" + color: "white" + font.family: "Inter" + font.pixelSize: 16 + font.weight: 700 + } + + ListView { + anchors { + fill: parent + margins: 4 + } + visible: audioPopup.audioSinks.length > 0 + model: audioPopup.audioSinks + clip: true + + delegate: Item { + id: sinkItem + required property var modelData + width: ListView.view.width + height: 48 + + Rectangle { + anchors.fill: parent + radius: 4 + color: sinkMouseArea.containsMouse ? "#1a1a1a" : "transparent" + } + + Text { + anchors { + left: parent.left + leftMargin: 10 + right: parent.right + rightMargin: 10 + verticalCenter: parent.verticalCenter + } + text: sinkItem.modelData.description || sinkItem.modelData.name + color: "white" + font.family: "Inter" + font.pixelSize: 16 + font.weight: 700 + elide: Text.ElideRight + } + + MouseArea { + id: sinkMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + Pipewire.preferredDefaultAudioSink = sinkItem.modelData + audioPopup.visible = false + } + } + } + } +} diff --git a/Bar.qml b/Bar.qml new file mode 100644 index 0000000..f0fd3cc --- /dev/null +++ b/Bar.qml @@ -0,0 +1,280 @@ +import QtQuick 2.15 +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Services.SystemTray +import Quickshell.Services.Pipewire +import QtQuick.Effects + +PanelWindow { + id: root + + property var niriWindowsList: [] + property var niriWorkspacesList: [] + + function windowIcon(appId) { + if (!appId) return Quickshell.iconPath("application-x-executable") + var entry = DesktopEntries.heuristicLookup(appId) + if (entry && entry.icon) + return Quickshell.iconPath(entry.icon, "application-x-executable") + return Quickshell.iconPath(appId, "application-x-executable") + } + + function px(val) { + return val / screen.devicePixelRatio + } + + visible: true + implicitHeight: px(60) + exclusiveZone: implicitHeight + exclusionMode: ExclusionMode.Auto + implicitWidth: screen.width + color: "black" + + anchors { + top: true + } + + property var activeWorkspace: niriWorkspacesList.find( + function(ws) { return ws.output === root.screen.name && ws.is_active }) || null + + property var monitorWindows: { + if (!root.activeWorkspace) return [] + var wsId = root.activeWorkspace.id + var wins = niriWindowsList.filter(function(w) { return w.workspace_id === wsId }) + wins.sort(function(a, b) { + var ap = a.layout && a.layout.pos_in_scrolling_layout + var bp = b.layout && b.layout.pos_in_scrolling_layout + if (ap && bp) return ap[0] !== bp[0] ? ap[0] - bp[0] : ap[1] - bp[1] + var ax = a.layout && a.layout.tile_pos_in_workspace_view ? a.layout.tile_pos_in_workspace_view[0] : 0 + var bx = b.layout && b.layout.tile_pos_in_workspace_view ? b.layout.tile_pos_in_workspace_view[0] : 0 + return ax - bx + }) + return wins.map(function(w) { + return Object.assign({}, w, {inViewport: w.is_focused}) + }) + } + + Process { + id: focusWindowProcess + property int targetId: 0 + command: ["niri", "msg", "action", "focus-window", "--id", targetId.toString()] + } + + property bool idleInhibitActive: false + + IdleInhibitor { + enabled: root.idleInhibitActive + window: root + } + + property var activeTrayMenu: null + property int trayMenuAnchorX: 0 + property var activeSubmenuEntry: null + + TrayMenuPopup { + id: trayMenuPopup + bar: root + } + + TraySubmenuPopup { + bar: root + mainPopup: trayMenuPopup + } + + AudioPopup { + id: audioPopup + bar: root + } + + // Left: window list + Row { + anchors { + left: parent.left + leftMargin: px(10) + verticalCenter: parent.verticalCenter + } + height: parent.height + spacing: 6 + + Repeater { + model: root.monitorWindows + + Item { + id: windowItem + required property var modelData + height: parent.height + width: 28 + + Rectangle { + anchors.centerIn: parent + width: 26 + height: 26 + radius: 6 + color: windowItem.modelData.inViewport ? "#555" : (hoverArea.containsMouse ? "#2a2a2a" : "transparent") + border.color: windowItem.modelData.inViewport ? "#888" : (hoverArea.containsMouse ? "#555" : "transparent") + border.width: 1 + + IconImage { + anchors.centerIn: parent + implicitSize: 16 + source: windowIcon(windowItem.modelData.app_id) + layer.enabled: !windowItem.modelData.inViewport + layer.effect: MultiEffect { + saturation: -1 + } + } + } + + MouseArea { + id: hoverArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + focusWindowProcess.targetId = windowItem.modelData.id + focusWindowProcess.running = true + } + } + } + } + } + + // Right: idle inhibit + audio + tray + clock + Row { + anchors { + right: parent.right + rightMargin: px(10) + verticalCenter: parent.verticalCenter + } + height: parent.height + spacing: px(4) + + Item { + width: 28 + height: parent.height + + Rectangle { + anchors.centerIn: parent + width: 26 + height: 26 + radius: 6 + color: root.idleInhibitActive ? "#3a2a10" : (idleInhibitArea.containsMouse ? "#1a1a1a" : "transparent") + border.color: root.idleInhibitActive ? "#c08030" : (idleInhibitArea.containsMouse ? "#555" : "transparent") + border.width: 1 + + Text { + anchors.centerIn: parent + text: "󰅶" + font.family: "Maple Mono" + font.pixelSize: 16 + color: root.idleInhibitActive ? "#c08030" : "#888" + } + } + + MouseArea { + id: idleInhibitArea + anchors.fill: parent + hoverEnabled: true + onClicked: root.idleInhibitActive = !root.idleInhibitActive + } + } + + Item { + width: audioContent.width + 8 + height: parent.height + + Row { + id: audioContent + anchors.centerIn: parent + spacing: 4 + + Text { + anchors.verticalCenter: parent.verticalCenter + text: "󰓃" + font.family: "Maple Mono" + font.pixelSize: 16 + color: "white" + } + + Text { + anchors.verticalCenter: parent.verticalCenter + text: Math.round((Pipewire.defaultAudioSink?.audio.volume ?? 0) * 100) + "%" + font.family: "Inter" + font.pixelSize: 13 + font.weight: 700 + color: "white" + } + } + + MouseArea { + anchors.fill: parent + onWheel: (wheel) => { + if (!Pipewire.defaultAudioSink) return + var delta = wheel.angleDelta.y > 0 ? 0.01 : -0.01 + Pipewire.defaultAudioSink.audio.volume = Math.max(0, Math.min(1, Pipewire.defaultAudioSink.audio.volume + delta)) + } + onClicked: audioPopup.visible = !audioPopup.visible + } + } + + Repeater { + model: SystemTray.items + + Item { + id: trayItem + required property SystemTrayItem modelData + width: 24 + height: parent.height + + Image { + anchors.centerIn: parent + width: 16 + height: 16 + source: trayItem.modelData.icon + sourceSize: Qt.size(16, 16) + smooth: true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: function(event) { + if (event.button === Qt.LeftButton) { + if (trayItem.modelData.hasMenu) { + if (trayMenuPopup.visible && root.activeTrayMenu === trayItem.modelData.menu) { + trayMenuPopup.visible = false + } else { + root.trayMenuAnchorX = trayItem.mapToItem(null, 0, 0).x + root.activeTrayMenu = trayItem.modelData.menu + trayMenuPopup.visible = true + } + } else { + trayItem.modelData.activate() + } + } + } + } + } + } + + Text { + id: timeText + width: px(120) + height: parent.height + verticalAlignment: Text.AlignVCenter + color: "white" + font.family: "Inter" + font.pixelSize: 16 + font.weight: 700 + text: Qt.formatDateTime(new Date(), "h:mm AP") + } + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: timeText.text = Qt.formatDateTime(new Date(), "h:mm AP") + } +} diff --git a/TrayMenuPopup.qml b/TrayMenuPopup.qml new file mode 100644 index 0000000..c0a7f42 --- /dev/null +++ b/TrayMenuPopup.qml @@ -0,0 +1,99 @@ +import QtQuick 2.15 +import Quickshell +import Quickshell.Services.SystemTray + +PopupWindow { + id: trayMenuPopup + + required property var bar + + anchor.window: bar + anchor.rect.x: bar.trayMenuAnchorX + anchor.rect.y: bar.implicitHeight + implicitWidth: 220 + implicitHeight: trayMenuCol.implicitHeight + 8 + grabFocus: true + color: "black" + onVisibleChanged: if (!visible) { bar.activeTrayMenu = null; bar.activeSubmenuEntry = null } + + QsMenuOpener { + id: trayMenuOpener + menu: bar.activeTrayMenu + } + + Column { + id: trayMenuCol + x: 4; y: 4 + width: parent.width - 8 + + Repeater { + model: trayMenuOpener.children + + delegate: Item { + id: menuEntry + required property var modelData + width: trayMenuCol.width + height: menuEntry.modelData.isSeparator ? 9 : 36 + + Rectangle { + visible: menuEntry.modelData.isSeparator + anchors.centerIn: parent + width: parent.width + height: 1 + color: "#333" + } + + Rectangle { + visible: !menuEntry.modelData.isSeparator + anchors.fill: parent + radius: 3 + color: menuItemArea.containsMouse && menuEntry.modelData.enabled ? "#1a1a1a" : "transparent" + + Text { + anchors { + left: parent.left + leftMargin: 10 + right: parent.right + rightMargin: menuEntry.modelData.hasChildren ? 24 : 10 + verticalCenter: parent.verticalCenter + } + text: menuEntry.modelData.text + color: menuEntry.modelData.enabled ? "white" : "#555" + font.family: "Inter" + font.pixelSize: 14 + font.weight: 700 + elide: Text.ElideRight + } + + Text { + visible: menuEntry.modelData.hasChildren + anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter } + text: "›" + color: "white" + font.family: "Inter" + font.pixelSize: 16 + } + + MouseArea { + id: menuItemArea + anchors.fill: parent + hoverEnabled: true + enabled: menuEntry.modelData.enabled && !menuEntry.modelData.isSeparator + onEntered: { + if (menuEntry.modelData.hasChildren) + bar.activeSubmenuEntry = menuEntry.modelData + else + bar.activeSubmenuEntry = null + } + onClicked: { + if (!menuEntry.modelData.hasChildren) { + menuEntry.modelData.triggered() + trayMenuPopup.visible = false + } + } + } + } + } + } + } +} diff --git a/TraySubmenuPopup.qml b/TraySubmenuPopup.qml new file mode 100644 index 0000000..3193ac5 --- /dev/null +++ b/TraySubmenuPopup.qml @@ -0,0 +1,81 @@ +import QtQuick 2.15 +import Quickshell + +PopupWindow { + id: submenuPopup + + required property var bar + required property var mainPopup + + anchor.window: bar + anchor.rect.x: bar.trayMenuAnchorX - 220 + anchor.rect.y: bar.implicitHeight + implicitWidth: 220 + implicitHeight: submenuCol.implicitHeight + 8 + visible: bar.activeSubmenuEntry !== null + color: "black" + + QsMenuOpener { + id: submenuOpener + menu: bar.activeSubmenuEntry + } + + Column { + id: submenuCol + x: 4; y: 4 + width: parent.width - 8 + + Repeater { + model: submenuOpener.children + + delegate: Item { + id: subEntry + required property var modelData + width: submenuCol.width + height: subEntry.modelData.isSeparator ? 9 : 36 + + Rectangle { + visible: subEntry.modelData.isSeparator + anchors.centerIn: parent + width: parent.width + height: 1 + color: "#333" + } + + Rectangle { + visible: !subEntry.modelData.isSeparator + anchors.fill: parent + radius: 3 + color: subItemArea.containsMouse && subEntry.modelData.enabled ? "#1a1a1a" : "transparent" + + Text { + anchors { + left: parent.left + leftMargin: 10 + right: parent.right + rightMargin: 10 + verticalCenter: parent.verticalCenter + } + text: subEntry.modelData.text + color: subEntry.modelData.enabled ? "white" : "#555" + font.family: "Inter" + font.pixelSize: 14 + font.weight: 700 + elide: Text.ElideRight + } + + MouseArea { + id: subItemArea + anchors.fill: parent + hoverEnabled: true + enabled: subEntry.modelData.enabled && !subEntry.modelData.isSeparator + onClicked: { + subEntry.modelData.triggered() + mainPopup.visible = false + } + } + } + } + } + } +} diff --git a/shell.qml b/shell.qml index f26c4dc..5a1463a 100644 --- a/shell.qml +++ b/shell.qml @@ -1,15 +1,7 @@ //@ pragma UseQApplication import QtQuick 2.15 -import QtQuick.Window 2.15 import Quickshell import Quickshell.Io -import Quickshell.Widgets -import Quickshell.Wayland -import Quickshell.Services.SystemTray -import Quickshell.Services.Pipewire -import QtQuick.Controls -import QtQuick.Effects -import QtQuick.Layouts ShellRoot { id: niriState @@ -66,513 +58,14 @@ ShellRoot { } } - Variants { - model: Quickshell.screens + Variants { + model: Quickshell.screens - PanelWindow { - id: root - property var modelData - screen: modelData - - function windowIcon(appId) { - if (!appId) return Quickshell.iconPath("application-x-executable") - var entry = DesktopEntries.heuristicLookup(appId) - if (entry && entry.icon) - return Quickshell.iconPath(entry.icon, "application-x-executable") - return Quickshell.iconPath(appId, "application-x-executable") - } - - function px(val) { - return val / screen.devicePixelRatio - } - - visible: true - implicitHeight: px(60) - exclusiveZone: implicitHeight - exclusionMode: ExclusionMode.Auto - implicitWidth: screen.width - color: "black" - - anchors { - top: true - } - - property var activeWorkspace: niriState.niriWorkspacesList.find( - function(ws) { return ws.output === root.screen.name && ws.is_active }) || null - - property var monitorWindows: { - if (!root.activeWorkspace) return [] - var wsId = root.activeWorkspace.id - var wins = niriState.niriWindowsList.filter(function(w) { return w.workspace_id === wsId }) - wins.sort(function(a, b) { - var ap = a.layout && a.layout.pos_in_scrolling_layout - var bp = b.layout && b.layout.pos_in_scrolling_layout - if (ap && bp) return ap[0] !== bp[0] ? ap[0] - bp[0] : ap[1] - bp[1] - var ax = a.layout && a.layout.tile_pos_in_workspace_view ? a.layout.tile_pos_in_workspace_view[0] : 0 - var bx = b.layout && b.layout.tile_pos_in_workspace_view ? b.layout.tile_pos_in_workspace_view[0] : 0 - return ax - bx - }) - return wins.map(function(w) { - return Object.assign({}, w, {inViewport: w.is_focused}) - }) - } - - Process { - id: focusWindowProcess - property int targetId: 0 - command: ["niri", "msg", "action", "focus-window", "--id", targetId.toString()] - } - - property bool idleInhibitActive: false - - IdleInhibitor { - id: idleInhibitor - enabled: root.idleInhibitActive - window: root - } - - property var activeTrayMenu: null - property int trayMenuAnchorX: 0 - property var activeSubmenuEntry: null - - QsMenuOpener { - id: trayMenuOpener - menu: root.activeTrayMenu - } - - QsMenuOpener { - id: submenuOpener - menu: root.activeSubmenuEntry - } - - PopupWindow { - id: trayMenuPopup - anchor.window: root - anchor.rect.x: root.trayMenuAnchorX - anchor.rect.y: root.implicitHeight - implicitWidth: 220 - implicitHeight: trayMenuCol.implicitHeight + 8 - grabFocus: true - color: "black" - onVisibleChanged: if (!visible) { root.activeTrayMenu = null; root.activeSubmenuEntry = null } - - Column { - id: trayMenuCol - x: 4; y: 4 - width: parent.width - 8 - - Repeater { - model: trayMenuOpener.children - - delegate: Item { - id: menuEntry - required property var modelData - width: trayMenuCol.width - height: menuEntry.modelData.isSeparator ? 9 : 36 - - Rectangle { - visible: menuEntry.modelData.isSeparator - anchors.centerIn: parent - width: parent.width - height: 1 - color: "#333" - } - - Rectangle { - visible: !menuEntry.modelData.isSeparator - anchors.fill: parent - radius: 3 - color: menuItemArea.containsMouse && menuEntry.modelData.enabled ? "#1a1a1a" : "transparent" - - Text { - anchors { - left: parent.left - leftMargin: 10 - right: parent.right - rightMargin: menuEntry.modelData.hasChildren ? 24 : 10 - verticalCenter: parent.verticalCenter - } - text: menuEntry.modelData.text - color: menuEntry.modelData.enabled ? "white" : "#555" - font.family: "Inter" - font.pixelSize: 14 - font.weight: 700 - elide: Text.ElideRight - } - - Text { - visible: menuEntry.modelData.hasChildren - anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter } - text: "›" - color: "white" - font.family: "Inter" - font.pixelSize: 16 - } - - MouseArea { - id: menuItemArea - anchors.fill: parent - hoverEnabled: true - enabled: menuEntry.modelData.enabled && !menuEntry.modelData.isSeparator - onEntered: { - if (menuEntry.modelData.hasChildren) - root.activeSubmenuEntry = menuEntry.modelData - else - root.activeSubmenuEntry = null - } - onClicked: { - if (!menuEntry.modelData.hasChildren) { - menuEntry.modelData.triggered() - trayMenuPopup.visible = false - } - } - } - } - } - } - } - } - PopupWindow { - id: submenuPopup - anchor.window: root - anchor.rect.x: root.trayMenuAnchorX - 220 - anchor.rect.y: root.implicitHeight - implicitWidth: 220 - implicitHeight: submenuCol.implicitHeight + 8 - visible: root.activeSubmenuEntry !== null - color: "black" - - Column { - id: submenuCol - x: 4; y: 4 - width: parent.width - 8 - - Repeater { - model: submenuOpener.children - - delegate: Item { - id: subEntry - required property var modelData - width: submenuCol.width - height: subEntry.modelData.isSeparator ? 9 : 36 - - Rectangle { - visible: subEntry.modelData.isSeparator - anchors.centerIn: parent - width: parent.width - height: 1 - color: "#333" - } - - Rectangle { - visible: !subEntry.modelData.isSeparator - anchors.fill: parent - radius: 3 - color: subItemArea.containsMouse && subEntry.modelData.enabled ? "#1a1a1a" : "transparent" - - Text { - anchors { - left: parent.left - leftMargin: 10 - right: parent.right - rightMargin: 10 - verticalCenter: parent.verticalCenter - } - text: subEntry.modelData.text - color: subEntry.modelData.enabled ? "white" : "#555" - font.family: "Inter" - font.pixelSize: 14 - font.weight: 700 - elide: Text.ElideRight - } - - MouseArea { - id: subItemArea - anchors.fill: parent - hoverEnabled: true - enabled: subEntry.modelData.enabled && !subEntry.modelData.isSeparator - onClicked: { - subEntry.modelData.triggered() - trayMenuPopup.visible = false - } - } - } - } - } - } - } - - property var audioSinks: Pipewire.nodes.values.filter(n => !n.isStream && n.isSink && n.audio !== null) - - // Bind the default sink so volume reads/writes are live - PwObjectTracker { - objects: Pipewire.defaultAudioSink ? [Pipewire.defaultAudioSink] : [] - } - - PopupWindow { - id: audioPopup - anchor.window: root - anchor.rect.x: root.width - 300 - anchor.rect.y: root.implicitHeight - implicitWidth: 300 - implicitHeight: root.audioSinks.length > 0 ? Math.min(root.audioSinks.length * 48 + 8, 400) : 52 - grabFocus: true - color: "black" - - Text { - anchors.centerIn: parent - visible: root.audioSinks.length === 0 - text: "No audio devices" - color: "white" - font.family: "Inter" - font.pixelSize: 16 - font.weight: 700 - } - - ListView { - anchors { - fill: parent - margins: 4 - } - visible: root.audioSinks.length > 0 - model: root.audioSinks - clip: true - - delegate: Item { - id: sinkItem - required property var modelData - width: ListView.view.width - height: 48 - - Rectangle { - anchors.fill: parent - radius: 4 - color: sinkMouseArea.containsMouse ? "#1a1a1a" : "transparent" - } - - Text { - anchors { - left: parent.left - leftMargin: 10 - right: parent.right - rightMargin: 10 - verticalCenter: parent.verticalCenter - } - text: sinkItem.modelData.description || sinkItem.modelData.name - color: "white" - font.family: "Inter" - font.pixelSize: 16 - font.weight: 700 - elide: Text.ElideRight - } - - MouseArea { - id: sinkMouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: { - Pipewire.preferredDefaultAudioSink = sinkItem.modelData - audioPopup.visible = false - } - } - } - } - } - - Row { - anchors { - left: parent.left - leftMargin: px(10) - verticalCenter: parent.verticalCenter - } - height: parent.height - spacing: 6 - - Repeater { - model: root.monitorWindows - - Item { - id: windowItem - required property var modelData - height: parent.height - width: 28 - - Rectangle { - anchors.centerIn: parent - width: 26 - height: 26 - radius: 6 - color: windowItem.modelData.inViewport ? "#555" : (hoverArea.containsMouse ? "#2a2a2a" : "transparent") - border.color: windowItem.modelData.inViewport ? "#888" : (hoverArea.containsMouse ? "#555" : "transparent") - border.width: 1 - - IconImage { - anchors.centerIn: parent - implicitSize: 16 - source: windowIcon(windowItem.modelData.app_id) - layer.enabled: !windowItem.modelData.inViewport - layer.effect: MultiEffect { - saturation: -1 - } - } - } - - MouseArea { - id: hoverArea - anchors.fill: parent - hoverEnabled: true - onClicked: { - focusWindowProcess.targetId = windowItem.modelData.id - focusWindowProcess.running = true - } - } - } - } - } - - Row { - anchors { - right: parent.right - rightMargin: px(10) - verticalCenter: parent.verticalCenter - } - height: parent.height - spacing: px(4) - - Item { - id: idleInhibitBtn - width: 28 - height: parent.height - - Rectangle { - anchors.centerIn: parent - width: 26 - height: 26 - radius: 6 - color: root.idleInhibitActive ? "#3a2a10" : (idleInhibitArea.containsMouse ? "#1a1a1a" : "transparent") - border.color: root.idleInhibitActive ? "#c08030" : (idleInhibitArea.containsMouse ? "#555" : "transparent") - border.width: 1 - - Text { - anchors.centerIn: parent - text: "󰅶" - font.family: "Maple Mono" - font.pixelSize: 16 - color: root.idleInhibitActive ? "#c08030" : "#888" - } - } - - MouseArea { - id: idleInhibitArea - anchors.fill: parent - hoverEnabled: true - onClicked: root.idleInhibitActive = !root.idleInhibitActive - } - } - - Item { - width: audioContent.width + 8 - height: parent.height - - Row { - id: audioContent - anchors.centerIn: parent - spacing: 4 - - Text { - anchors.verticalCenter: parent.verticalCenter - text: "󰓃" - font.family: "Maple Mono" - font.pixelSize: 16 - color: "white" - } - - Text { - anchors.verticalCenter: parent.verticalCenter - text: Math.round((Pipewire.defaultAudioSink?.audio.volume ?? 0) * 100) + "%" - font.family: "Inter" - font.pixelSize: 13 - font.weight: 700 - color: "white" - } - } - - MouseArea { - anchors.fill: parent - onWheel: (wheel) => { - if (!Pipewire.defaultAudioSink) return - var delta = wheel.angleDelta.y > 0 ? 0.01 : -0.01 - Pipewire.defaultAudioSink.audio.volume = Math.max(0, Math.min(1, Pipewire.defaultAudioSink.audio.volume + delta)) - } - onClicked: { - audioPopup.visible = !audioPopup.visible - } - } - } - - Repeater { - model: SystemTray.items - - Item { - id: trayItem - required property SystemTrayItem modelData - - width: 24 - height: parent.height - - Image { - anchors.centerIn: parent - width: 16 - height: 16 - source: trayItem.modelData.icon - sourceSize: Qt.size(16, 16) - smooth: true - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: function(event) { - if (event.button === Qt.LeftButton) { - if (trayItem.modelData.hasMenu) { - if (trayMenuPopup.visible && root.activeTrayMenu === trayItem.modelData.menu) { - trayMenuPopup.visible = false - } else { - root.trayMenuAnchorX = trayItem.mapToItem(null, 0, 0).x - root.activeTrayMenu = trayItem.modelData.menu - trayMenuPopup.visible = true - } - } else { - trayItem.modelData.activate() - } - } - } - } - } - } - - Text { - id: timeText - width: px(120) - height: parent.height - verticalAlignment: Text.AlignVCenter - - color: "white" - font.family: "Inter" - font.pixelSize: 16 - font.weight: 700 - text: Qt.formatDateTime(new Date(), "h:mm AP") - } - } - - Timer { - interval: 1000 - running: true - repeat: true - onTriggered: { - timeText.text = Qt.formatDateTime(new Date(), "h:mm AP") + Bar { + property var modelData + screen: modelData + niriWindowsList: niriState.niriWindowsList + niriWorkspacesList: niriState.niriWorkspacesList } } - } // PanelWindow - } // Variants -} // ShellRoot +}