quickshell: add parent wrapper to all dropdowns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
rope 2026-05-27 11:30:10 +01:00
parent 179b44d319
commit dfc4a6b509

View file

@ -1330,65 +1330,73 @@ in
id: menuOpener id: menuOpener
} }
Column { Rectangle {
id: menuItems
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 6 anchors.topMargin: 6
width: 200 width: menuItems.width + 16
height: menuItems.height + 12
radius: 10
color: Theme.base01
Repeater { Column {
model: menuOpener.children id: menuItems
anchors.centerIn: parent
width: 200
Rectangle { Repeater {
required property var modelData model: menuOpener.children
width: 200
height: modelData.isSeparator ? 9 : 28
color: !modelData.isSeparator && itemMouse.containsMouse && modelData.enabled
? Theme.base02 : "transparent"
radius: modelData.isSeparator ? 0 : 4
Rectangle { Rectangle {
visible: modelData.isSeparator required property var modelData
anchors.centerIn: parent width: 200
width: parent.width - 20 height: modelData.isSeparator ? 9 : 28
height: 1 color: !modelData.isSeparator && itemMouse.containsMouse && modelData.enabled
color: Theme.base03 ? Theme.base02 : "transparent"
} radius: modelData.isSeparator ? 0 : 4
RowLayout { Rectangle {
visible: !modelData.isSeparator visible: modelData.isSeparator
anchors.fill: parent anchors.centerIn: parent
anchors.leftMargin: 10 width: parent.width - 20
anchors.rightMargin: 10 height: 1
spacing: 8 color: Theme.base03
Text {
Layout.fillWidth: true
text: modelData.text ?? ""
color: modelData.enabled ? Theme.base05 : Theme.base03
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
elide: Text.ElideRight
} }
Text { RowLayout {
visible: modelData.buttonType !== QsMenuButtonType.None visible: !modelData.isSeparator
text: modelData.checkState === Qt.Checked ? "\u2713" : "" anchors.fill: parent
color: Theme.base0D anchors.leftMargin: 10
font.family: "FiraMono Nerd Font" anchors.rightMargin: 10
font.pixelSize: 12 spacing: 8
}
}
MouseArea { Text {
id: itemMouse Layout.fillWidth: true
anchors.fill: parent text: modelData.text ?? ""
hoverEnabled: true color: modelData.enabled ? Theme.base05 : Theme.base03
enabled: !modelData.isSeparator && modelData.enabled font.family: "FiraMono Nerd Font"
onClicked: { font.pixelSize: 12
modelData.triggered(); elide: Text.ElideRight
bar.closeAllDropdowns(); }
Text {
visible: modelData.buttonType !== QsMenuButtonType.None
text: modelData.checkState === Qt.Checked ? "\u2713" : ""
color: Theme.base0D
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
}
}
MouseArea {
id: itemMouse
anchors.fill: parent
hoverEnabled: true
enabled: !modelData.isSeparator && modelData.enabled
onClicked: {
modelData.triggered();
bar.closeAllDropdowns();
}
} }
} }
} }
@ -1404,184 +1412,192 @@ in
fullHeight: volDropdownCol.height + 16 fullHeight: volDropdownCol.height + 16
autoCloseMs: 3000 autoCloseMs: 3000
Column { Rectangle {
id: volDropdownCol
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 8 anchors.topMargin: 8
width: 260 width: volDropdownCol.width + 20
spacing: 8 height: volDropdownCol.height + 16
radius: 10
color: Theme.base01
// Master volume Column {
Text { id: volDropdownCol
text: "\u{f057e} Master" anchors.centerIn: parent
color: Theme.base05 width: 260
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
font.weight: Font.Medium
}
Row {
width: parent.width
spacing: 8 spacing: 8
Rectangle { // Master volume
id: masterSliderBg Text {
width: parent.width - masterVolLabel.width - 8 text: "\u{f057e} Master"
height: 20 color: Theme.base05
radius: 4 font.family: "FiraMono Nerd Font"
color: Theme.base01 font.pixelSize: 13
anchors.verticalCenter: parent.verticalCenter font.weight: Font.Medium
}
Row {
width: parent.width
spacing: 8
Rectangle { Rectangle {
width: volWidget.sink && volWidget.sink.audio id: masterSliderBg
? Math.min(1, volWidget.sink.audio.volume) * parent.width : 0 width: parent.width - masterVolLabel.width - 8
height: parent.height height: 20
radius: 4 radius: 4
color: volWidget.muted ? Theme.base03 : Theme.base0D color: Theme.base02
Behavior on width { NumberAnimation { duration: 80 } } anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: volWidget.sink && volWidget.sink.audio
? Math.min(1, volWidget.sink.audio.volume) * parent.width : 0
height: parent.height
radius: 4
color: volWidget.muted ? Theme.base03 : Theme.base0D
Behavior on width { NumberAnimation { duration: 80 } }
}
MouseArea {
anchors.fill: parent
onPressed: (mouse) => setVolume(mouse)
onPositionChanged: (mouse) => { if (pressed) setVolume(mouse); }
function setVolume(mouse) {
if (!volWidget.sink || !volWidget.sink.audio) return;
let v = Math.max(0, Math.min(1, mouse.x / width));
volWidget.sink.audio.volume = v;
}
}
} }
Text {
id: masterVolLabel
width: 36
text: volWidget.vol + "%"
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
}
// Mute button
Rectangle {
width: parent.width
height: 28
color: masterMuteMa.containsMouse ? Theme.base02 : "transparent"
radius: 4
Text {
anchors.centerIn: parent
text: volWidget.muted ? "\u{f0581} Unmute" : "\u{f057e} Mute"
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
}
MouseArea { MouseArea {
id: masterMuteMa
anchors.fill: parent anchors.fill: parent
onPressed: (mouse) => setVolume(mouse) hoverEnabled: true
onPositionChanged: (mouse) => { if (pressed) setVolume(mouse); } cursorShape: Qt.PointingHandCursor
function setVolume(mouse) { onClicked: {
if (!volWidget.sink || !volWidget.sink.audio) return; if (volWidget.sink && volWidget.sink.audio)
let v = Math.max(0, Math.min(1, mouse.x / width)); volWidget.sink.audio.muted = !volWidget.sink.audio.muted;
volWidget.sink.audio.volume = v;
} }
} }
} }
// Separator
Rectangle {
width: parent.width - 20
anchors.horizontalCenter: parent.horizontalCenter
height: 1
color: Theme.base02
visible: appStreamsCol.childrenRect.height > 0
}
// App streams header
Text { Text {
id: masterVolLabel visible: appStreamsCol.childrenRect.height > 0
width: 36 text: "\u{f0641} Applications"
text: volWidget.vol + "%"
color: Theme.base05 color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 13
horizontalAlignment: Text.AlignRight font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
} }
}
// Mute button // Per-app streams
Rectangle { Column {
width: parent.width id: appStreamsCol
height: 28 width: parent.width
color: masterMuteMa.containsMouse ? Theme.base02 : "transparent" spacing: 6
radius: 4
Text { Repeater {
anchors.centerIn: parent id: appStreamsRepeater
text: volWidget.muted ? "\u{f0581} Unmute" : "\u{f057e} Mute" model: Pipewire.nodes
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
}
MouseArea {
id: masterMuteMa
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (volWidget.sink && volWidget.sink.audio)
volWidget.sink.audio.muted = !volWidget.sink.audio.muted;
}
}
}
// Separator Column {
Rectangle { required property var modelData
width: parent.width - 20
anchors.horizontalCenter: parent.horizontalCenter
height: 1
color: Theme.base02
visible: appStreamsCol.childrenRect.height > 0
}
// App streams header
Text {
visible: appStreamsCol.childrenRect.height > 0
text: "\u{f0641} Applications"
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
font.weight: Font.Medium
}
// Per-app streams
Column {
id: appStreamsCol
width: parent.width
spacing: 6
Repeater {
id: appStreamsRepeater
model: Pipewire.nodes
Column {
required property var modelData
width: parent.width
spacing: 2
visible: modelData.isStream && modelData.audio !== null
PwObjectTracker {
objects: [modelData]
}
Text {
text: modelData.properties["application.name"] || modelData.name || "Unknown"
color: Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
elide: Text.ElideRight
width: parent.width width: parent.width
} spacing: 2
visible: modelData.isStream && modelData.audio !== null
Row { PwObjectTracker {
width: parent.width objects: [modelData]
spacing: 8
Rectangle {
width: parent.width - appVolLabel.width - 8
height: 16
radius: 3
color: Theme.base01
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: modelData.audio
? Math.min(1, modelData.audio.volume) * parent.width : 0
height: parent.height
radius: 3
color: modelData.audio && modelData.audio.muted
? Theme.base03 : Theme.base0C
Behavior on width { NumberAnimation { duration: 80 } }
}
MouseArea {
anchors.fill: parent
onPressed: (mouse) => setVol(mouse)
onPositionChanged: (mouse) => { if (pressed) setVol(mouse); }
function setVol(mouse) {
if (!modelData.audio) return;
let v = Math.max(0, Math.min(1, mouse.x / width));
modelData.audio.volume = v;
}
}
} }
Text { Text {
id: appVolLabel text: modelData.properties["application.name"] || modelData.name || "Unknown"
width: 36
text: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : "0%"
color: Theme.base04 color: Theme.base04
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 10 font.pixelSize: 11
horizontalAlignment: Text.AlignRight elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter width: parent.width
}
Row {
width: parent.width
spacing: 8
Rectangle {
width: parent.width - appVolLabel.width - 8
height: 16
radius: 3
color: Theme.base02
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: modelData.audio
? Math.min(1, modelData.audio.volume) * parent.width : 0
height: parent.height
radius: 3
color: modelData.audio && modelData.audio.muted
? Theme.base03 : Theme.base0C
Behavior on width { NumberAnimation { duration: 80 } }
}
MouseArea {
anchors.fill: parent
onPressed: (mouse) => setVol(mouse)
onPositionChanged: (mouse) => { if (pressed) setVol(mouse); }
function setVol(mouse) {
if (!modelData.audio) return;
let v = Math.max(0, Math.min(1, mouse.x / width));
modelData.audio.volume = v;
}
}
}
Text {
id: appVolLabel
width: 36
text: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : "0%"
color: Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 10
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
} }
} }
} }
@ -1597,135 +1613,143 @@ in
fullWidth: netDropdownCol.width + 24 fullWidth: netDropdownCol.width + 24
fullHeight: netDropdownCol.height + 16 fullHeight: netDropdownCol.height + 16
Column { Rectangle {
id: netDropdownCol
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 8 anchors.topMargin: 8
width: 220 width: netDropdownCol.width + 20
spacing: 4 height: netDropdownCol.height + 16
radius: 10
color: Theme.base01
Text { Column {
width: parent.width id: netDropdownCol
text: netWidget.netState === "connected" anchors.centerIn: parent
? "\u{f05a9} " + netWidget.netConn width: 220
: "\u{f05aa} Not connected" spacing: 4
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
font.weight: Font.Medium
elide: Text.ElideRight
}
Rectangle {
visible: netWidget.netState === "connected"
width: parent.width
height: 28
color: disconnectMouse.containsMouse ? Theme.base02 : "transparent"
radius: 4
Text { Text {
anchors.centerIn: parent width: parent.width
text: "Disconnect" text: netWidget.netState === "connected"
color: Theme.base08 ? "\u{f05a9} " + netWidget.netConn
: "\u{f05aa} Not connected"
color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 12 font.pixelSize: 13
font.weight: Font.Medium
elide: Text.ElideRight
} }
MouseArea {
id: disconnectMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
netDisconnectProc.targetDevice = netWidget.netDevice;
netDisconnectProc.running = true;
netWidget.netState = "disconnected";
netWidget.netConn = "";
netWidget.netIcon = "\u{f05aa}";
bar.closeAllDropdowns();
netRefreshDelay.start();
}
}
}
Rectangle {
width: parent.width - 20
anchors.horizontalCenter: parent.horizontalCenter
height: 1
color: Theme.base03
}
Text {
text: "Available networks"
color: Theme.base03
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
topPadding: 2
}
Repeater {
model: netWidget.wifiNetworks
Rectangle { Rectangle {
required property var modelData visible: netWidget.netState === "connected"
width: 220 width: parent.width
height: 32 height: 28
color: netItemMouse.containsMouse ? Theme.base02 : "transparent" color: disconnectMouse.containsMouse ? Theme.base02 : "transparent"
radius: 4 radius: 4
Row { Text {
anchors.verticalCenter: parent.verticalCenter anchors.centerIn: parent
anchors.left: parent.left text: "Disconnect"
anchors.leftMargin: 8 color: Theme.base08
anchors.right: parent.right font.family: "FiraMono Nerd Font"
anchors.rightMargin: 8 font.pixelSize: 12
spacing: 8
Text {
text: {
let s = modelData.signal;
if (s >= 75) return "\u{f05a9}";
if (s >= 50) return "\u{f05a9}";
if (s >= 25) return "\u{f05a9}";
return "\u{f05aa}";
}
color: modelData.active ? Theme.base0B : Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: modelData.ssid
color: modelData.active ? Theme.base0B : Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
elide: Text.ElideRight
width: 140
anchors.verticalCenter: parent.verticalCenter
}
Text {
visible: modelData.security !== "" && modelData.security !== "--"
text: "\u{f0341}"
color: Theme.base03
font.family: "FiraMono Nerd Font"
font.pixelSize: 10
anchors.verticalCenter: parent.verticalCenter
}
} }
MouseArea { MouseArea {
id: netItemMouse id: disconnectMouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
if (!modelData.active) { netDisconnectProc.targetDevice = netWidget.netDevice;
wifiConnectProc.targetSsid = modelData.ssid; netDisconnectProc.running = true;
wifiConnectProc.running = true; netWidget.netState = "disconnected";
netRefreshDelay.start(); netWidget.netConn = "";
} netWidget.netIcon = "\u{f05aa}";
bar.closeAllDropdowns(); bar.closeAllDropdowns();
netRefreshDelay.start();
}
}
}
Rectangle {
width: parent.width - 20
anchors.horizontalCenter: parent.horizontalCenter
height: 1
color: Theme.base03
}
Text {
text: "Available networks"
color: Theme.base03
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
topPadding: 2
}
Repeater {
model: netWidget.wifiNetworks
Rectangle {
required property var modelData
width: 220
height: 32
color: netItemMouse.containsMouse ? Theme.base02 : "transparent"
radius: 4
Row {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
anchors.right: parent.right
anchors.rightMargin: 8
spacing: 8
Text {
text: {
let s = modelData.signal;
if (s >= 75) return "\u{f05a9}";
if (s >= 50) return "\u{f05a9}";
if (s >= 25) return "\u{f05a9}";
return "\u{f05aa}";
}
color: modelData.active ? Theme.base0B : Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: modelData.ssid
color: modelData.active ? Theme.base0B : Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
elide: Text.ElideRight
width: 140
anchors.verticalCenter: parent.verticalCenter
}
Text {
visible: modelData.security !== "" && modelData.security !== "--"
text: "\u{f0341}"
color: Theme.base03
font.family: "FiraMono Nerd Font"
font.pixelSize: 10
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: netItemMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (!modelData.active) {
wifiConnectProc.targetSsid = modelData.ssid;
wifiConnectProc.running = true;
netRefreshDelay.start();
}
bar.closeAllDropdowns();
}
} }
} }
} }
@ -1741,109 +1765,117 @@ in
fullWidth: batteryDropdownCol.width + 24 fullWidth: batteryDropdownCol.width + 24
fullHeight: batteryDropdownCol.height + 16 fullHeight: batteryDropdownCol.height + 16
Column { Rectangle {
id: batteryDropdownCol
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 10 anchors.topMargin: 8
width: 200 width: batteryDropdownCol.width + 20
spacing: 8 height: batteryDropdownCol.height + 16
radius: 10
color: Theme.base01
Row { Column {
width: parent.width id: batteryDropdownCol
anchors.centerIn: parent
width: 200
spacing: 8 spacing: 8
Text { Row {
text: batteryWidget.batteryIcon width: parent.width
color: Theme.base05 spacing: 8
font.family: "FiraMono Nerd Font"
font.pixelSize: 18
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: batteryWidget.batteryLevel + "%" + (batteryWidget.charging ? " Charging" : "") text: batteryWidget.batteryIcon
color: Theme.base05 color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 18
font.weight: Font.Medium anchors.verticalCenter: parent.verticalCenter
} }
Text {
text: batteryWidget.powerDraw.toFixed(1) + " W" Column {
+ (batteryWidget.timeRemaining !== "" ? " \u2022 " + batteryWidget.timeRemaining + (batteryWidget.charging ? " to full" : " left") : "") anchors.verticalCenter: parent.verticalCenter
color: Theme.base04 Text {
font.family: "FiraMono Nerd Font" text: batteryWidget.batteryLevel + "%" + (batteryWidget.charging ? " Charging" : "")
font.pixelSize: 11 color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
font.weight: Font.Medium
}
Text {
text: batteryWidget.powerDraw.toFixed(1) + " W"
+ (batteryWidget.timeRemaining !== "" ? " \u2022 " + batteryWidget.timeRemaining + (batteryWidget.charging ? " to full" : " left") : "")
color: Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
}
} }
} }
}
Rectangle { Rectangle {
width: parent.width - 10 width: parent.width - 10
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
height: 1 height: 1
color: Theme.base03 color: Theme.base03
} }
Text { Text {
text: "Power Profile" text: "Power Profile"
color: Theme.base03 color: Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
} }
Row { Row {
width: parent.width width: parent.width
spacing: 4 spacing: 4
Repeater { Repeater {
model: [ model: [
{ name: "power-saver", label: "\u{f0425}", tip: "Saver" }, { name: "power-saver", label: "\u{f0425}", tip: "Saver" },
{ name: "balanced", label: "\u{f0376}", tip: "Balanced" }, { name: "balanced", label: "\u{f0376}", tip: "Balanced" },
{ name: "performance", label: "\u{f0e0e}", tip: "Performance" } { name: "performance", label: "\u{f0e0e}", tip: "Performance" }
] ]
Rectangle { Rectangle {
required property var modelData required property var modelData
width: (parent.width - 8) / 3 width: (parent.width - 8) / 3
height: 36 height: 36
radius: 6 radius: 6
color: batteryWidget.powerProfile === modelData.name color: batteryWidget.powerProfile === modelData.name
? Theme.base02 : profMouse.containsMouse ? Theme.base02 : profMouse.containsMouse
? Theme.base01 : "transparent" ? Theme.base01 : "transparent"
border.width: batteryWidget.powerProfile === modelData.name ? 1 : 0 border.width: batteryWidget.powerProfile === modelData.name ? 1 : 0
border.color: Theme.base03 border.color: Theme.base03
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 1 spacing: 1
Text { Text {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: modelData.label text: modelData.label
color: batteryWidget.powerProfile === modelData.name color: batteryWidget.powerProfile === modelData.name
? Theme.base0D : Theme.base05 ? Theme.base0D : Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 14 font.pixelSize: 14
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: modelData.tip
color: Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 9
}
} }
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: modelData.tip
color: Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 9
}
}
MouseArea { MouseArea {
id: profMouse id: profMouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
setProfileProc.target = modelData.name; setProfileProc.target = modelData.name;
setProfileProc.running = true; setProfileProc.running = true;
batteryWidget.powerProfile = modelData.name; batteryWidget.powerProfile = modelData.name;
}
} }
} }
} }