Give every hero toggle a keyboard focus ring
Each panel's hero on/off toggle (Tailscale, Dropbox Wi-Fi, Bluetooth, network, audio mute) now has a reachable "header" cursor section, a focus ring around the hero icon, Enter/Space activation, a letter shortcut, and hover parity. The hero is inset by heroRingPad so the ring stays inside the Flickable/ScrollView clip box instead of being cut off. Bluetooth and audio get a virtual "header" section above their device sections, so the adapter/mute can be toggled by keyboard even when no device rows exist. Network repurposes its previously dead header section. For the PanelHero-based panels (Tailscale, Dropbox), route panel state through the wrapper's `header` id: inside a PanelHero iconComponent `root` resolves to PanelHero, not the Panel, so `root.headerHasCursor` and `root.setHeaderCursor()` silently referenced the wrong object. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
835181f112
commit
228b5ac3ba
@@ -38,6 +38,8 @@ Panel {
|
||||
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
||||
readonly property color iconColor: dropbox.authenticated && dropbox.active ? foreground : dim
|
||||
readonly property color barIconColor: dropbox.authenticated && dropbox.active ? barForeground : Qt.darker(barForeground, 1.55)
|
||||
readonly property bool headerHasCursor: cursorActive && focusSection === "header"
|
||||
readonly property int heroRingPad: Style.space(6)
|
||||
|
||||
function ensureCursor() {
|
||||
if (!dropbox.authenticated) {
|
||||
@@ -50,7 +52,7 @@ Panel {
|
||||
fileIndex = 0
|
||||
return
|
||||
}
|
||||
if (focusSection !== "files") focusSection = "files"
|
||||
if (focusSection !== "files" && focusSection !== "header") focusSection = "files"
|
||||
if (fileIndex >= dropbox.files.length) fileIndex = Math.max(0, dropbox.files.length - 1)
|
||||
if (fileIndex < 0) fileIndex = 0
|
||||
}
|
||||
@@ -58,15 +60,39 @@ Panel {
|
||||
function moveCursor(dx, dy) {
|
||||
cursorActive = true
|
||||
ensureCursor()
|
||||
if (focusSection === "files" && dy !== 0) {
|
||||
if (dy === 0) return
|
||||
if (focusSection === "header") {
|
||||
if (dy > 0 && dropbox.files.length > 0) {
|
||||
focusSection = "files"
|
||||
fileIndex = 0
|
||||
scrollCursorIntoView()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (focusSection === "files") {
|
||||
if (dy < 0 && fileIndex === 0) {
|
||||
setHeaderCursor()
|
||||
return
|
||||
}
|
||||
fileIndex = Math.max(0, Math.min(dropbox.files.length - 1, fileIndex + dy))
|
||||
scrollCursorIntoView()
|
||||
}
|
||||
}
|
||||
|
||||
function setHeaderCursor() {
|
||||
cursorActive = true
|
||||
focusSection = "header"
|
||||
if (panelFlick) panelFlick.contentY = 0
|
||||
}
|
||||
|
||||
function toggleRunning() {
|
||||
if (dropbox.installed && !dropbox.busy) dropbox.toggleRunning()
|
||||
}
|
||||
|
||||
function activateCursor() {
|
||||
ensureCursor()
|
||||
if (focusSection === "login") dropbox.login()
|
||||
else if (focusSection === "header") toggleRunning()
|
||||
else if (focusSection === "files") dropbox.openFile(selectedFile())
|
||||
}
|
||||
|
||||
@@ -183,6 +209,7 @@ Panel {
|
||||
onTextKey: function(t) {
|
||||
if (t === "r" || t === "R") dropbox.refresh()
|
||||
else if (t === "l" || t === "L") dropbox.login()
|
||||
else if (t === "p" || t === "P") root.toggleRunning()
|
||||
}
|
||||
|
||||
Flickable {
|
||||
@@ -201,40 +228,66 @@ Panel {
|
||||
width: panelFlick.width
|
||||
spacing: Style.space(12)
|
||||
|
||||
PanelHero {
|
||||
id: hero
|
||||
Item {
|
||||
id: header
|
||||
visible: dropbox.authenticated
|
||||
width: parent.width
|
||||
title: "Dropbox"
|
||||
meta: dropbox.active ? root.heroPhraseText : "Syncing paused"
|
||||
foreground: root.foreground
|
||||
fontFamily: root.fontFamily
|
||||
iconOpacity: dropbox.active ? 1.0 : 0.5
|
||||
iconComponent: Component {
|
||||
Item {
|
||||
implicitWidth: heroIcon.implicitWidth
|
||||
implicitHeight: heroIcon.implicitHeight
|
||||
implicitHeight: hero.implicitHeight + root.heroRingPad
|
||||
// Exposed for the hero's iconComponent, whose `root` resolves to
|
||||
// PanelHero (not this Panel) — reach panel state via `header`.
|
||||
readonly property bool ringVisible: root.headerHasCursor
|
||||
readonly property int ringPad: root.heroRingPad
|
||||
function focusHero() { root.setHeaderCursor() }
|
||||
|
||||
DropboxIcon {
|
||||
id: heroIcon
|
||||
iconSize: Style.font.display
|
||||
color: root.iconColor
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
PanelHero {
|
||||
id: hero
|
||||
x: root.heroRingPad
|
||||
y: root.heroRingPad
|
||||
width: parent.width - root.heroRingPad
|
||||
title: "Dropbox"
|
||||
meta: dropbox.active ? root.heroPhraseText : "Syncing paused"
|
||||
foreground: root.foreground
|
||||
fontFamily: root.fontFamily
|
||||
iconOpacity: dropbox.active ? 1.0 : 0.5
|
||||
iconComponent: Component {
|
||||
Item {
|
||||
implicitWidth: heroIcon.implicitWidth
|
||||
implicitHeight: heroIcon.implicitHeight
|
||||
|
||||
MouseArea {
|
||||
id: heroMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: dropbox.installed && !dropbox.busy
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: dropbox.toggleRunning()
|
||||
}
|
||||
// Keyboard focus ring around the hero toggle. The hero is
|
||||
// inset by heroRingPad so this ring stays inside the
|
||||
// Flickable's clip box instead of being cut off.
|
||||
BorderSurface {
|
||||
anchors.fill: heroIcon
|
||||
anchors.margins: -header.ringPad
|
||||
color: "transparent"
|
||||
radius: Style.cornerRadius
|
||||
visible: header.ringVisible
|
||||
borderSpec: Border.controlSpec("hover-cursor", hero.foreground, Color.accent)
|
||||
}
|
||||
|
||||
PanelToolTip {
|
||||
visible: heroMouse.containsMouse
|
||||
text: dropbox.active ? "Pause syncing" : "Resume syncing"
|
||||
fontFamily: root.fontFamily
|
||||
DropboxIcon {
|
||||
id: heroIcon
|
||||
iconSize: Style.font.display
|
||||
color: root.iconColor
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: heroMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: dropbox.installed && !dropbox.busy
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onContainsMouseChanged: if (containsMouse) header.focusHero()
|
||||
onClicked: dropbox.toggleRunning()
|
||||
}
|
||||
|
||||
PanelToolTip {
|
||||
visible: heroMouse.containsMouse
|
||||
text: dropbox.active ? "Pause syncing" : "Resume syncing"
|
||||
fontFamily: root.fontFamily
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user