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
}
Column {
id: menuItems
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 6
width: 200
width: menuItems.width + 16
height: menuItems.height + 12
radius: 10
color: Theme.base01
Repeater {
model: menuOpener.children
Column {
id: menuItems
anchors.centerIn: parent
width: 200
Rectangle {
required property var modelData
width: 200
height: modelData.isSeparator ? 9 : 28
color: !modelData.isSeparator && itemMouse.containsMouse && modelData.enabled
? Theme.base02 : "transparent"
radius: modelData.isSeparator ? 0 : 4
Repeater {
model: menuOpener.children
Rectangle {
visible: modelData.isSeparator
anchors.centerIn: parent
width: parent.width - 20
height: 1
color: Theme.base03
}
required property var modelData
width: 200
height: modelData.isSeparator ? 9 : 28
color: !modelData.isSeparator && itemMouse.containsMouse && modelData.enabled
? Theme.base02 : "transparent"
radius: modelData.isSeparator ? 0 : 4
RowLayout {
visible: !modelData.isSeparator
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 8
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
Rectangle {
visible: modelData.isSeparator
anchors.centerIn: parent
width: parent.width - 20
height: 1
color: Theme.base03
}
Text {
visible: modelData.buttonType !== QsMenuButtonType.None
text: modelData.checkState === Qt.Checked ? "\u2713" : ""
color: Theme.base0D
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
}
}
RowLayout {
visible: !modelData.isSeparator
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 8
MouseArea {
id: itemMouse
anchors.fill: parent
hoverEnabled: true
enabled: !modelData.isSeparator && modelData.enabled
onClicked: {
modelData.triggered();
bar.closeAllDropdowns();
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 {
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
autoCloseMs: 3000
Column {
id: volDropdownCol
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 8
width: 260
spacing: 8
width: volDropdownCol.width + 20
height: volDropdownCol.height + 16
radius: 10
color: Theme.base01
// Master volume
Text {
text: "\u{f057e} Master"
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
font.weight: Font.Medium
}
Row {
width: parent.width
Column {
id: volDropdownCol
anchors.centerIn: parent
width: 260
spacing: 8
Rectangle {
id: masterSliderBg
width: parent.width - masterVolLabel.width - 8
height: 20
radius: 4
color: Theme.base01
anchors.verticalCenter: parent.verticalCenter
// Master volume
Text {
text: "\u{f057e} Master"
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
font.weight: Font.Medium
}
Row {
width: parent.width
spacing: 8
Rectangle {
width: volWidget.sink && volWidget.sink.audio
? Math.min(1, volWidget.sink.audio.volume) * parent.width : 0
height: parent.height
id: masterSliderBg
width: parent.width - masterVolLabel.width - 8
height: 20
radius: 4
color: volWidget.muted ? Theme.base03 : Theme.base0D
Behavior on width { NumberAnimation { duration: 80 } }
color: Theme.base02
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 {
id: masterMuteMa
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;
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (volWidget.sink && volWidget.sink.audio)
volWidget.sink.audio.muted = !volWidget.sink.audio.muted;
}
}
}
// Separator
Rectangle {
width: parent.width - 20
anchors.horizontalCenter: parent.horizontalCenter
height: 1
color: Theme.base02
visible: appStreamsCol.childrenRect.height > 0
}
// App streams header
Text {
id: masterVolLabel
width: 36
text: volWidget.vol + "%"
visible: appStreamsCol.childrenRect.height > 0
text: "\u{f0641} Applications"
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 13
font.weight: Font.Medium
}
}
// Mute button
Rectangle {
width: parent.width
height: 28
color: masterMuteMa.containsMouse ? Theme.base02 : "transparent"
radius: 4
// Per-app streams
Column {
id: appStreamsCol
width: parent.width
spacing: 6
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 {
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;
}
}
}
Repeater {
id: appStreamsRepeater
model: Pipewire.nodes
// Separator
Rectangle {
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
Column {
required property var modelData
width: parent.width
}
spacing: 2
visible: modelData.isStream && modelData.audio !== null
Row {
width: parent.width
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;
}
}
PwObjectTracker {
objects: [modelData]
}
Text {
id: appVolLabel
width: 36
text: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : "0%"
text: modelData.properties["application.name"] || modelData.name || "Unknown"
color: Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 10
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 11
elide: Text.ElideRight
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
fullHeight: netDropdownCol.height + 16
Column {
id: netDropdownCol
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 8
width: 220
spacing: 4
width: netDropdownCol.width + 20
height: netDropdownCol.height + 16
radius: 10
color: Theme.base01
Text {
width: parent.width
text: netWidget.netState === "connected"
? "\u{f05a9} " + netWidget.netConn
: "\u{f05aa} Not connected"
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
Column {
id: netDropdownCol
anchors.centerIn: parent
width: 220
spacing: 4
Text {
anchors.centerIn: parent
text: "Disconnect"
color: Theme.base08
width: parent.width
text: netWidget.netState === "connected"
? "\u{f05a9} " + netWidget.netConn
: "\u{f05aa} Not connected"
color: Theme.base05
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 {
required property var modelData
width: 220
height: 32
color: netItemMouse.containsMouse ? Theme.base02 : "transparent"
visible: netWidget.netState === "connected"
width: parent.width
height: 28
color: disconnectMouse.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
}
Text {
anchors.centerIn: parent
text: "Disconnect"
color: Theme.base08
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
}
MouseArea {
id: netItemMouse
id: disconnectMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (!modelData.active) {
wifiConnectProc.targetSsid = modelData.ssid;
wifiConnectProc.running = true;
netRefreshDelay.start();
}
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 {
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
fullHeight: batteryDropdownCol.height + 16
Column {
id: batteryDropdownCol
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
width: 200
spacing: 8
anchors.topMargin: 8
width: batteryDropdownCol.width + 20
height: batteryDropdownCol.height + 16
radius: 10
color: Theme.base01
Row {
width: parent.width
Column {
id: batteryDropdownCol
anchors.centerIn: parent
width: 200
spacing: 8
Text {
text: batteryWidget.batteryIcon
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 18
anchors.verticalCenter: parent.verticalCenter
}
Row {
width: parent.width
spacing: 8
Column {
anchors.verticalCenter: parent.verticalCenter
Text {
text: batteryWidget.batteryLevel + "%" + (batteryWidget.charging ? " Charging" : "")
text: batteryWidget.batteryIcon
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 13
font.weight: Font.Medium
font.pixelSize: 18
anchors.verticalCenter: parent.verticalCenter
}
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
Column {
anchors.verticalCenter: parent.verticalCenter
Text {
text: batteryWidget.batteryLevel + "%" + (batteryWidget.charging ? " Charging" : "")
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 {
width: parent.width - 10
anchors.horizontalCenter: parent.horizontalCenter
height: 1
color: Theme.base03
}
Rectangle {
width: parent.width - 10
anchors.horizontalCenter: parent.horizontalCenter
height: 1
color: Theme.base03
}
Text {
text: "Power Profile"
color: Theme.base03
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
}
Text {
text: "Power Profile"
color: Theme.base03
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
}
Row {
width: parent.width
spacing: 4
Row {
width: parent.width
spacing: 4
Repeater {
model: [
{ name: "power-saver", label: "\u{f0425}", tip: "Saver" },
{ name: "balanced", label: "\u{f0376}", tip: "Balanced" },
{ name: "performance", label: "\u{f0e0e}", tip: "Performance" }
]
Repeater {
model: [
{ name: "power-saver", label: "\u{f0425}", tip: "Saver" },
{ name: "balanced", label: "\u{f0376}", tip: "Balanced" },
{ name: "performance", label: "\u{f0e0e}", tip: "Performance" }
]
Rectangle {
required property var modelData
width: (parent.width - 8) / 3
height: 36
radius: 6
color: batteryWidget.powerProfile === modelData.name
? Theme.base02 : profMouse.containsMouse
? Theme.base01 : "transparent"
border.width: batteryWidget.powerProfile === modelData.name ? 1 : 0
border.color: Theme.base03
Rectangle {
required property var modelData
width: (parent.width - 8) / 3
height: 36
radius: 6
color: batteryWidget.powerProfile === modelData.name
? Theme.base02 : profMouse.containsMouse
? Theme.base01 : "transparent"
border.width: batteryWidget.powerProfile === modelData.name ? 1 : 0
border.color: Theme.base03
Column {
anchors.centerIn: parent
spacing: 1
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: modelData.label
color: batteryWidget.powerProfile === modelData.name
? Theme.base0D : Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 14
Column {
anchors.centerIn: parent
spacing: 1
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: modelData.label
color: batteryWidget.powerProfile === modelData.name
? Theme.base0D : Theme.base05
font.family: "FiraMono Nerd Font"
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 {
id: profMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
setProfileProc.target = modelData.name;
setProfileProc.running = true;
batteryWidget.powerProfile = modelData.name;
MouseArea {
id: profMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
setProfileProc.target = modelData.name;
setProfileProc.running = true;
batteryWidget.powerProfile = modelData.name;
}
}
}
}