features/adding_quickshell #6

Merged
thomas merged 48 commits from features/adding_quickshell into main 2026-08-27 14:55:30 +01:00
Showing only changes of commit d6f79685f0 - Show all commits

View File

@@ -127,6 +127,11 @@ Item {
d.forget() d.forget()
} }
function setTrusted(row, trusted) {
const d = root.deviceForAddress(row.address)
if (d) d.trusted = trusted
}
readonly property bool anyConnected: root.connectedRows.length > 0 readonly property bool anyConnected: root.connectedRows.length > 0
// Caps how tall the Paired/Available list gets before it scrolls // Caps how tall the Paired/Available list gets before it scrolls
@@ -203,8 +208,13 @@ Item {
} }
} }
// One device row. `showForget` hides the forget (x) button for // One device row. `showForget` gates the whole trailing action cluster —
// discovered-but-unpaired devices, which have nothing to forget yet. // trust toggle, disconnect, forget — for discovered-but-unpaired devices,
// which have none of those to act on yet (a plain click there pairs
// instead, via activate()). For a paired/connected row, the row body
// itself still connects/disconnects (activate()) same as before; the
// actions below are the explicit, discoverable versions of that plus
// the two this row previously had no way to reach at all.
component DeviceRow: Rectangle { component DeviceRow: Rectangle {
id: devRow id: devRow
required property var dev required property var dev
@@ -213,6 +223,27 @@ Item {
width: parent ? parent.width : 0 width: parent ? parent.width : 0
radius: 0 radius: 0
implicitHeight: rowContent.implicitHeight + 12 implicitHeight: rowContent.implicitHeight + 12
// Which action icon (if any) the pointer is currently over — drives
// both that icon's own hover color below and rowMouse's click
// dispatch. Derived from rowMouse.mouseX/containsMouse rather than a
// MouseArea on each icon: those icons live inside rowContent, which
// sits underneath rowMouse in stacking order, so a nested MouseArea
// there would just be shadowed by it (same reason
// NetworkIndicator.qml's NetworkRow dispatches by position too).
// Checked right-to-left since that's the order the icons are laid
// out in, so an earlier icon's "from here to the row's right edge"
// zone doesn't swallow the ones after it.
readonly property string hoveredAction: {
if (!rowMouse.containsMouse) return ""
if (devRow.showForget && forgetIcon.visible
&& rowMouse.mouseX >= devRow.mapFromItem(forgetIcon, 0, 0).x - 8) return "forget"
if (devRow.showForget && trustIcon.visible
&& rowMouse.mouseX >= devRow.mapFromItem(trustIcon, 0, 0).x - 8) return "trust"
if (disconnectIcon.visible
&& rowMouse.mouseX >= devRow.mapFromItem(disconnectIcon, 0, 0).x - 8) return "disconnect"
return ""
}
// Hover checked first (same ordering as Volume.qml's DeviceRow): // Hover checked first (same ordering as Volume.qml's DeviceRow):
// hovering always shows a solid Colors.primary fill, even for an // hovering always shows a solid Colors.primary fill, even for an
// already-connected row, so the primaryText used below never has to // already-connected row, so the primaryText used below never has to
@@ -283,14 +314,97 @@ Item {
} }
} }
Text { // Each action below is a fixed-size slot with its own
// hover-halo outline — offset outside the slot rather than drawn
// on its own edge, same treatment as devRow's own outline above
// and every other hoverable box in this file (ToggleSwitch,
// DeviceRow itself, etc.) — instead of just the color swap the
// row's own icons get.
// Disconnect without forgetting — same effect as clicking the
// row body on a Connected row (activate() below calls
// disconnectDevice too), just given its own explicit, always-
// visible icon instead of relying on that being guessed.
Item {
id: disconnectIcon
visible: devRow.showForget && devRow.dev.connected
Layout.preferredWidth: 20
Layout.preferredHeight: 20
Rectangle {
anchors.fill: parent
anchors.margins: -3
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: devRow.hoveredAction === "disconnect" ? 1 : 0
}
Text {
anchors.centerIn: parent
text: "link_off"
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
// Stays primaryText (dark) rather than switching to
// Colors.primary while targeted — devRow's own fill is
// already Colors.primary on hover, so that would've put
// a primary-colored glyph on a primary-colored
// background. The outline Rectangle above is what shows
// which slot is targeted.
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
// Trust toggle — full brightness when trusted, dimmed when not,
// same "state via opacity" convention PowerButton.qml uses for
// its disabled entries.
Item {
id: trustIcon
visible: devRow.showForget
Layout.preferredWidth: 20
Layout.preferredHeight: 20
Rectangle {
anchors.fill: parent
anchors.margins: -3
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: devRow.hoveredAction === "trust" ? 1 : 0
}
Text {
anchors.centerIn: parent
text: "verified_user"
opacity: devRow.dev.trusted ? 1 : 0.4
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
Item {
id: forgetIcon id: forgetIcon
visible: devRow.showForget visible: devRow.showForget
text: "close" Layout.preferredWidth: 20
font.family: Appearance.iconFontFamilyRounded Layout.preferredHeight: 20
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14 Rectangle {
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted anchors.fill: parent
anchors.margins: -3
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: devRow.hoveredAction === "forget" ? 1 : 0
}
Text {
anchors.centerIn: parent
text: "close"
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
} }
} }
@@ -302,21 +416,15 @@ Item {
height: rowContent.height + 12 height: rowContent.height + 12
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
// forgetIcon has no MouseArea of its own — nested inside // Dispatches on devRow.hoveredAction rather than its own
// rowContent, which sits underneath this full-row area in // MouseArea per icon — see that property's comment above.
// stacking order, so a separate MouseArea there would just be onClicked: {
// shadowed (see the identical fix in NetworkIndicator.qml's switch (devRow.hoveredAction) {
// NetworkRow). Dispatch by click position against this single case "forget": root.forgetDevice(devRow.dev); break
// area instead. case "trust": root.setTrusted(devRow.dev, !devRow.dev.trusted); break
onClicked: (mouse) => { case "disconnect": root.disconnectDevice(devRow.dev); break
if (devRow.showForget && forgetIcon.visible) { default: devRow.activate()
const forgetLeft = devRow.mapFromItem(forgetIcon, 0, 0).x
if (mouse.x >= forgetLeft - 8) {
root.forgetDevice(devRow.dev)
return
}
} }
devRow.activate()
} }
} }
} }
@@ -327,7 +435,11 @@ Item {
id: panel id: panel
visible: false visible: false
color: "transparent" color: "transparent"
readonly property int contentWidth: 244 // Wider than the other panels' usual 244 — each device row now ends
// in a row of three 20px action slots (disconnect/trust/forget)
// instead of a single close icon, and the name column needs room to
// not immediately start eliding against that.
readonly property int contentWidth: 300
readonly property int contentPadding: 16 readonly property int contentPadding: 16
implicitWidth: contentWidth + contentPadding * 2 implicitWidth: contentWidth + contentPadding * 2
implicitHeight: mainColumn.implicitHeight + contentPadding * 2 implicitHeight: mainColumn.implicitHeight + contentPadding * 2
@@ -470,6 +582,42 @@ Item {
} }
} }
// Paired — always shown in full, same as Connected above and
// for the same reason: these are one click away from
// reconnecting (root.connectDevice reconnects rather than
// re-pairing whenever a row is already paired/bonded/trusted),
// so they shouldn't be buried in the capped-height scroll area
// below or hidden behind an active scan turning up other
// nearby devices.
Column {
width: parent.width
spacing: 4
visible: root.knownRows.length > 0
Text {
text: "Paired Devices"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Column {
// Inset 3px on each side to match deviceListColumn
// below, so DeviceRow's hover halo has room here too.
x: 3
width: parent.width - 6
spacing: 2
Repeater {
model: root.knownRows
DeviceRow {
required property var modelData
dev: modelData
showForget: true
onActivate: root.connectDevice(dev)
}
}
}
}
Text { Text {
width: parent.width width: parent.width
visible: root.adapter !== null && root.adapter.enabled && root.deviceRows.length === 0 visible: root.adapter !== null && root.adapter.enabled && root.deviceRows.length === 0
@@ -479,9 +627,9 @@ Item {
color: Colors.muted color: Colors.muted
} }
// Paired/Available — capped height, scrolls once there are // Available — freshly discovered, unpaired devices only.
// more devices than fit rather than growing the popup // Capped height, scrolls once there are more than fit rather
// indefinitely. // than growing the popup indefinitely.
Flickable { Flickable {
id: deviceListFlick id: deviceListFlick
width: parent.width width: parent.width
@@ -507,32 +655,6 @@ Item {
bottomPadding: 3 bottomPadding: 3
spacing: 6 spacing: 6
Column {
width: parent.width
spacing: 4
visible: root.knownRows.length > 0
Text {
text: "Paired Devices"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Column {
width: parent.width
spacing: 2
Repeater {
model: root.knownRows
DeviceRow {
required property var modelData
dev: modelData
showForget: true
onActivate: root.connectDevice(dev)
}
}
}
}
Column { Column {
width: parent.width width: parent.width
spacing: 4 spacing: 4