Skip to content

Commit 88f9941

Browse files
committed
fix: resolve Tab key focus navigation issues in notification center
- Implemented manual focus control for Tab/Shift+Tab between header and notify items - Added focus cycling signals and helper functions for proper keyboard navigation - Disabled default activeFocusOnTab to prevent focus jumping issues Log: resolve Tab key focus navigation issues in notification center pms: BUG-339893 pms: BUG-339891
1 parent fd0768d commit 88f9941

File tree

9 files changed

+435
-21
lines changed

9 files changed

+435
-21
lines changed

panels/notification/center/GroupNotify.qml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ NotifyItem {
1515
implicitHeight: impl.implicitHeight
1616

1717
signal collapse()
18+
signal gotoNextItem() // Signal to navigate to next notify item
19+
signal gotoPrevItem() // Signal to navigate to previous notify item
20+
21+
// Focus the first button for Tab navigation into group
22+
function focusFirstButton() {
23+
foldBtn.forceActiveFocus()
24+
return true
25+
}
1826

1927
Control {
2028
id: impl
@@ -34,28 +42,62 @@ NotifyItem {
3442
}
3543

3644
AnimationSettingButton {
45+
id: foldBtn
3746
Layout.alignment: Qt.AlignRight
47+
activeFocusOnTab: false
48+
forcusBorderVisible: activeFocus
3849
icon.name: "fold"
3950
text: qsTr("Fold")
51+
Keys.onTabPressed: function(event) {
52+
groupMoreBtn.forceActiveFocus()
53+
event.accepted = true
54+
}
55+
Keys.onBacktabPressed: function(event) {
56+
root.gotoPrevItem()
57+
event.accepted = true
58+
}
4059
onClicked: {
4160
console.log("collapse")
4261
root.collapse()
4362
}
4463
}
4564
AnimationSettingButton {
65+
id: groupMoreBtn
4666
Layout.alignment: Qt.AlignRight
67+
activeFocusOnTab: false
68+
forcusBorderVisible: activeFocus
4769
icon.name: "more"
4870
text: qsTr("More")
71+
Keys.onTabPressed: function(event) {
72+
groupClearBtn.forceActiveFocus()
73+
event.accepted = true
74+
}
75+
Keys.onBacktabPressed: function(event) {
76+
foldBtn.forceActiveFocus()
77+
event.accepted = true
78+
}
4979
onClicked: function () {
5080
console.log("group setting", root.appName)
5181
let pos = mapToItem(root, Qt.point(width / 2, height))
5282
root.setting(pos)
5383
}
5484
}
5585
AnimationSettingButton {
86+
id: groupClearBtn
5687
Layout.alignment: Qt.AlignRight
88+
activeFocusOnTab: false
89+
forcusBorderVisible: activeFocus
5790
icon.name: "clean-group"
5891
text: qsTr("Clear All")
92+
Keys.onTabPressed: function(event) {
93+
groupClearBtn.focus = false // Clear focus before signal to prevent focus state residue
94+
root.gotoNextItem()
95+
event.accepted = true
96+
}
97+
Keys.onBacktabPressed: function(event) {
98+
groupMoreBtn.forceActiveFocus()
99+
event.accepted = true
100+
}
59101
onClicked: function () {
60102
root.remove()
61103
}

panels/notification/center/NormalNotify.qml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ NotifyItem {
1414
implicitWidth: impl.implicitWidth
1515
implicitHeight: impl.implicitHeight
1616

17+
signal gotoNextItem()
18+
signal gotoPrevItem()
19+
20+
function focusFirstButton() {
21+
return notifyContent.focusFirstButton()
22+
}
23+
1724
Control {
1825
id: impl
1926
anchors.fill: parent
2027

2128
contentItem: NotifyItemContent {
29+
id: notifyContent
2230
width: parent.width
2331
appName: root.appName
2432
iconName: root.iconName
@@ -42,6 +50,8 @@ NotifyItem {
4250
onActionInvoked: function (actionId) {
4351
root.actionInvoked(actionId)
4452
}
53+
onGotoNextItem: root.gotoNextItem()
54+
onGotoPrevItem: root.gotoPrevItem()
4555
}
4656
}
4757
}

panels/notification/center/NotifyCenter.qml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ FocusScope {
3636
width: NotifyStyle.contentItem.width
3737
notifyModel: notifyModel
3838
z: 1
39+
onGotoFirstNotify: {
40+
if (view.viewCount === 0 || !view.focusItemAtIndex(0)) header.focusFirstButton()
41+
}
42+
onGotoLastNotify: {
43+
if (view.viewCount === 0) header.focusLastButton()
44+
else view.focusLastItem()
45+
}
3946
}
4047

4148
NotifyView {
@@ -51,6 +58,8 @@ FocusScope {
5158

5259
height: Math.min(maxViewHeight, viewHeight)
5360
notifyModel: notifyModel
61+
onGotoHeaderFirst: header.focusFirstButton()
62+
onGotoHeaderLast: header.focusLastButton()
5463
}
5564

5665
DropShadowText {

panels/notification/center/NotifyHeader.qml

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,38 @@ import org.deepin.ds.notificationcenter
1111

1212
FocusScope {
1313
id: root
14+
activeFocusOnTab: true
1415

1516
required property NotifyModel notifyModel
1617

18+
signal gotoFirstNotify() // Signal to Tab to first notify item
19+
signal gotoLastNotify() // Signal to Shift+Tab to last notify item
20+
21+
// Forward focus to first button when FocusScope receives focus
22+
onActiveFocusChanged: {
23+
if (activeFocus && !collapseBtn.activeFocus && !moreBtn.activeFocus && !clearAllBtn.activeFocus) {
24+
if (collapseBtn.visible) {
25+
collapseBtn.forceActiveFocus()
26+
} else {
27+
moreBtn.forceActiveFocus()
28+
}
29+
}
30+
}
31+
32+
// Focus the first visible button in header for Tab navigation
33+
function focusFirstButton() {
34+
if (collapseBtn.visible) {
35+
collapseBtn.forceActiveFocus()
36+
} else {
37+
moreBtn.forceActiveFocus()
38+
}
39+
}
40+
41+
// Focus the last button in header for Shift+Tab navigation
42+
function focusLastButton() {
43+
clearAllBtn.forceActiveFocus()
44+
}
45+
1746
RowLayout {
1847
anchors.fill: parent
1948
NotifyHeaderTitleText {
@@ -29,35 +58,71 @@ FocusScope {
2958
}
3059

3160
AnimationSettingButton {
61+
id: collapseBtn
3262
objectName: "collapse"
33-
focus: true
3463
visible: !notifyModel.collapse
3564
Layout.alignment: Qt.AlignRight
65+
activeFocusOnTab: false
66+
forcusBorderVisible: activeFocus
3667
icon.name: "fold"
3768
text: qsTr("Fold")
69+
Keys.onTabPressed: function(event) {
70+
moreBtn.forceActiveFocus()
71+
event.accepted = true
72+
}
73+
Keys.onBacktabPressed: function(event) {
74+
root.gotoLastNotify()
75+
event.accepted = true
76+
}
3877
onClicked: function () {
3978
console.log("Collapse all notify")
4079
notifyModel.collapseAllApp()
4180
}
4281
}
4382

4483
AnimationSettingButton {
84+
id: moreBtn
4585
objectName: "more"
46-
focus: true
4786
Layout.alignment: Qt.AlignRight
87+
activeFocusOnTab: false
88+
forcusBorderVisible: activeFocus
4889
icon.name: "more"
4990
text: qsTr("More")
91+
Keys.onTabPressed: function(event) {
92+
clearAllBtn.forceActiveFocus()
93+
event.accepted = true
94+
}
95+
Keys.onBacktabPressed: function(event) {
96+
if (collapseBtn.visible) {
97+
collapseBtn.forceActiveFocus()
98+
} else {
99+
root.gotoLastNotify()
100+
}
101+
event.accepted = true
102+
}
50103
onClicked: function () {
51104
console.log("Notify setting")
52105
NotifyAccessor.openNotificationSetting()
53106
}
54107
}
55108

56109
AnimationSettingButton {
110+
id: clearAllBtn
57111
objectName: "closeAllNotify"
112+
activeFocusOnTab: false
113+
forcusBorderVisible: activeFocus
58114
icon.name: "clean-all"
59115
text: qsTr("Clear All")
60116
Layout.alignment: Qt.AlignRight
117+
Keys.onTabPressed: function(event) {
118+
clearAllBtn.focus = false // Clear focus before signal to prevent focus state residue
119+
root.gotoFirstNotify()
120+
event.accepted = true
121+
}
122+
Keys.onBacktabPressed: function(event) {
123+
moreBtn.forceActiveFocus()
124+
event.accepted = true
125+
}
61126
onClicked: function () {
62127
console.log("Clear all notify")
63128
notifyModel.clear()

panels/notification/center/NotifyView.qml

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,52 @@ Control {
1818
readonly property real viewHeight: view.contentHeight
1919
readonly property int viewCount: view.count
2020

21+
signal gotoHeaderFirst() // Signal to cycle Tab back to header first button
22+
signal gotoHeaderLast() // Signal to cycle Shift+Tab back to header last button
23+
2124
NotifySetting {
2225
id: notifySetting
2326
notifyModel: root.notifyModel
2427
}
2528

29+
// Focus notify item at specified index with retry logic for delegate creation
30+
function focusItemAtIndex(idx) {
31+
if (idx < 0 || idx >= view.count) return false
32+
view.currentIndex = idx
33+
view.positionViewAtIndex(idx, ListView.Contain)
34+
function tryFocus(retries) {
35+
let item = view.itemAtIndex(idx)
36+
if (item) {
37+
item.forceActiveFocus()
38+
} else if (retries > 0) {
39+
Qt.callLater(function() { tryFocus(retries - 1) })
40+
}
41+
}
42+
Qt.callLater(function() { tryFocus(5) })
43+
return true
44+
}
45+
46+
// Focus the last notify item for Shift+Tab cycling from header
47+
function focusLastItem() {
48+
if (view.count > 0) {
49+
focusItemAtIndex(view.count - 1)
50+
}
51+
}
52+
2653
contentItem: ListView {
2754
id: view
2855
spacing: 10
2956
snapMode: ListView.SnapToItem
30-
// activeFocusOnTab: true
57+
keyNavigationEnabled: false
58+
activeFocusOnTab: false
3159
ScrollBar.vertical: ScrollBar { }
3260
property int nextIndex: -1
3361
property bool panelShown: false
34-
62+
63+
// Forward signals from delegate to root for Tab cycling
64+
function gotoHeaderFirst() { root.gotoHeaderFirst() }
65+
function gotoHeaderLast() { root.gotoHeaderLast() }
66+
3567
onNextIndexChanged: {
3668
if (nextIndex >= 0 && count > 0) {
3769
currentIndex = nextIndex
@@ -63,7 +95,6 @@ Control {
6395
properties: "y"
6496
from: {
6597
if (addTrans.ViewTransition.item.objectName.startsWith("overlap-")) {
66-
// 24: group notify overlap height
6798
return addTrans.ViewTransition.destination.y + view.spacing + 24
6899
} else if (addTrans.ViewTransition.item.indexInGroup === 0) {
69100
return addTrans.ViewTransition.destination.y - view.spacing - 24

0 commit comments

Comments
 (0)