Refine Bluetooth panel device actions

This commit is contained in:
David Heinemeier Hansson
2026-05-22 13:26:19 +02:00
parent 274259934f
commit 94530d89de
+81 -37
View File
@@ -115,6 +115,7 @@ Panel {
// guaranteeing one highlight on screen. // guaranteeing one highlight on screen.
property string focusSection: "connected" property string focusSection: "connected"
property int selectedIndex: 0 property int selectedIndex: 0
property bool actionFocused: false
property bool cursorActive: false property bool cursorActive: false
// Stable identity for the focused device. Devices move between sections as // Stable identity for the focused device. Devices move between sections as
@@ -246,24 +247,43 @@ Panel {
var sections = visibleSections var sections = visibleSections
if (!sections || sections.length === 0) return if (!sections || sections.length === 0) return
var sIdx = sections.indexOf(focusSection) var sIdx = sections.indexOf(focusSection)
if (sIdx < 0) { focusSection = sections[0]; selectedIndex = 0; return } if (sIdx < 0) { focusSection = sections[0]; selectedIndex = 0; actionFocused = false; return }
var idx = selectedIndex var idx = selectedIndex
var max = sectionCount(focusSection) - 1 var max = sectionCount(focusSection) - 1
if (delta > 0) { if (delta > 0) {
if (idx < max) { selectedIndex = idx + 1; return } if (idx < max) { selectedIndex = idx + 1; actionFocused = false; return }
if (sIdx < sections.length - 1) { if (sIdx < sections.length - 1) {
focusSection = sections[sIdx + 1] focusSection = sections[sIdx + 1]
selectedIndex = 0 selectedIndex = 0
actionFocused = false
} }
} else { } else {
if (idx > 0) { selectedIndex = idx - 1; return } if (idx > 0) { selectedIndex = idx - 1; actionFocused = false; return }
if (sIdx > 0) { focusSection = sections[sIdx - 1]; selectedIndex = sectionCount(focusSection) - 1 } if (sIdx > 0) {
focusSection = sections[sIdx - 1]
selectedIndex = sectionCount(focusSection) - 1
actionFocused = false
}
} }
} }
function moveCursorH(delta) {
if (!cursorActive) { cursorActive = true; return }
if (focusSection !== "known") return
var dev = deviceAt(focusSection, selectedIndex)
if (!dev || dev.connected) return
if (delta > 0) actionFocused = true
else if (delta < 0) actionFocused = false
}
function activateCursor() { function activateCursor() {
if (actionFocused) {
deleteSelected()
return
}
if (focusSection === "connected" || focusSection === "known") { if (focusSection === "connected" || focusSection === "known") {
var dev = deviceAt(focusSection, selectedIndex) var dev = deviceAt(focusSection, selectedIndex)
if (!dev) return if (!dev) return
@@ -278,17 +298,14 @@ Panel {
} }
} }
// 'x' on a known row mirrors the row's X button: connected device // 'x' forgets remembered devices. Connected rows toggle connection via
// disconnects, everything else forgets the pairing. Mismatching this // Enter/click, so the destructive forget action is intentionally unavailable
// (e.g. forgetting a connected device) is destructive — the X button // while connected.
// tooltip says "Disconnect" for connected rows, and the keybind has
// to agree.
function deleteSelected() { function deleteSelected() {
if (focusSection !== "connected" && focusSection !== "known") return if (focusSection !== "known") return
var dev = deviceAt(focusSection, selectedIndex) var dev = deviceAt(focusSection, selectedIndex)
if (!dev) return if (!dev) return
if (dev.connected) disconnectDevice(dev) forgetDevice(dev)
else forgetDevice(dev)
} }
onOpenedChanged: { onOpenedChanged: {
@@ -297,6 +314,7 @@ Panel {
if (connectedDevices.length > 0) { focusSection = "connected"; selectedIndex = 0 } if (connectedDevices.length > 0) { focusSection = "connected"; selectedIndex = 0 }
else if (knownDevices.length > 0) { focusSection = "known"; selectedIndex = 0 } else if (knownDevices.length > 0) { focusSection = "known"; selectedIndex = 0 }
else if (discoveredDevices.length > 0) { focusSection = "discovered"; selectedIndex = 0 } else if (discoveredDevices.length > 0) { focusSection = "discovered"; selectedIndex = 0 }
actionFocused = false
cursorActive = false cursorActive = false
} }
} }
@@ -464,6 +482,7 @@ Panel {
onMoveRequested: function(dx, dy) { onMoveRequested: function(dx, dy) {
if (!root.cursorActive) { root.cursorActive = true; return } if (!root.cursorActive) { root.cursorActive = true; return }
if (dy !== 0) root.moveCursor(dy) if (dy !== 0) root.moveCursor(dy)
else if (dx !== 0) root.moveCursorH(dx)
} }
onActivateRequested: if (root.cursorActive) root.activateCursor() onActivateRequested: if (root.cursorActive) root.activateCursor()
onCloseRequested: root.close() onCloseRequested: root.close()
@@ -650,8 +669,18 @@ Panel {
readonly property bool isConnected: dev && dev.connected readonly property bool isConnected: dev && dev.connected
readonly property int devState: dev && dev.state !== undefined ? dev.state : -1 readonly property int devState: dev && dev.state !== undefined ? dev.state : -1
readonly property string action: root.pendingAction(dev ? dev.address : "") readonly property string action: root.pendingAction(dev ? dev.address : "")
readonly property string actionTooltip: {
if (!dev) return ""
if (isConnected) return "Disconnect"
if (isDiscovered) return "Pair"
return "Connect"
}
hasCursor: root.cursorActive && root.focusSection === sectionName && root.selectedIndex === rowIndex readonly property bool rowSelected: root.cursorActive && root.focusSection === sectionName && root.selectedIndex === rowIndex
readonly property bool forgetAvailable: sectionName === "known" && !isConnected && !isDiscovered
readonly property bool showForgetButton: forgetAvailable && (rowMouse.containsMouse || rowSelected)
hasCursor: rowSelected && !root.actionFocused
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(row) onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(row)
current: isConnected current: isConnected
foreground: root.bar.foreground foreground: root.bar.foreground
@@ -690,6 +719,7 @@ Panel {
root.cursorActive = true root.cursorActive = true
root.focusSection = row.sectionName root.focusSection = row.sectionName
root.selectedIndex = row.rowIndex root.selectedIndex = row.rowIndex
root.actionFocused = false
} }
onClicked: function(mouse) { onClicked: function(mouse) {
@@ -699,11 +729,17 @@ Panel {
else if (!row.isDiscovered) root.forgetDevice(row.dev) else if (!row.isDiscovered) root.forgetDevice(row.dev)
return return
} }
if (row.isConnected) return // use the X button to disconnect if (row.isConnected) root.disconnectDevice(row.dev)
root.connectDevice(row.dev) else root.connectDevice(row.dev)
} }
} }
PanelToolTip {
visible: row.actionTooltip !== "" && rowMouse.containsMouse && !root.actionFocused
text: row.actionTooltip
fontFamily: root.bar.fontFamily
}
Item { Item {
id: rowContent id: rowContent
anchors.left: parent.left anchors.left: parent.left
@@ -711,7 +747,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.space(10) anchors.leftMargin: Style.space(10)
anchors.rightMargin: Style.space(10) anchors.rightMargin: Style.space(10)
implicitHeight: Math.max(deviceIcon.implicitHeight, info.implicitHeight, disconnectBtn.implicitHeight) implicitHeight: Math.max(deviceIcon.implicitHeight, info.implicitHeight, forgetBtn.implicitHeight)
Text { Text {
id: deviceIcon id: deviceIcon
@@ -723,32 +759,13 @@ Panel {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Explicit close button on any known device. Action depends on state:
// connected -> disconnect, otherwise -> forget the pairing entirely.
PanelActionButton {
id: disconnectBtn
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: !row.isDiscovered
iconText: "󰅙"
tooltipText: row.isConnected ? "Disconnect" : "Forget"
foreground: root.bar.foreground
hoverColor: root.bar.urgent
fontFamily: root.bar.fontFamily
onClicked: {
if (!row.dev) return
if (row.isConnected) root.disconnectDevice(row.dev)
else root.forgetDevice(row.dev)
}
}
Column { Column {
id: info id: info
spacing: Style.space(1) spacing: Style.space(1)
anchors.left: deviceIcon.right anchors.left: deviceIcon.right
anchors.leftMargin: Style.space(10) anchors.leftMargin: Style.space(10)
anchors.right: disconnectBtn.visible ? disconnectBtn.left : parent.right anchors.right: forgetBtn.visible ? forgetBtn.left : parent.right
anchors.rightMargin: disconnectBtn.visible ? Style.space(8) : 0 anchors.rightMargin: forgetBtn.visible ? Style.space(8) : 0
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
@@ -769,6 +786,33 @@ Panel {
width: parent.width width: parent.width
} }
} }
PanelActionButton {
id: forgetBtn
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: row.showForgetButton
iconText: "󰅙"
tooltipText: "Forget"
foreground: root.bar.foreground
hoverColor: root.bar.foreground
fontFamily: root.bar.fontFamily
hasCursor: row.rowSelected && root.actionFocused
onHovered: function(isHovered) {
if (!isHovered) {
if (rowMouse.containsMouse) root.actionFocused = false
return
}
root.cursorActive = true
root.focusSection = row.sectionName
root.selectedIndex = row.rowIndex
root.actionFocused = true
}
onClicked: {
if (!row.dev) return
root.forgetDevice(row.dev)
}
}
} }
} }
} }