Skip to content

fix(notification): hide close button when mouse leaves center after e…#1427

Open
re2zero wants to merge 1 commit intolinuxdeepin:masterfrom
re2zero:bugfix
Open

fix(notification): hide close button when mouse leaves center after e…#1427
re2zero wants to merge 1 commit intolinuxdeepin:masterfrom
re2zero:bugfix

Conversation

@re2zero
Copy link
Contributor

@re2zero re2zero commented Feb 4, 2026

…xpand

Added mouse position tracking to notification center to prevent close button from staying visible after expand when mouse moves outside the center area.

在通知中心添加鼠标位置追踪,解决展开后鼠标移出中心时关闭按钮不隐藏的问题。

Log: 修复展开通知后鼠标移出中心关闭按钮不隐藏的问题
PMS: BUG-347241
Influence: 修复后,展开折叠通知并将鼠标移出通知中心区域时,展开的第一条信息右上角的关闭按钮会正确隐藏,提升用户体验。同时保留所有Tab键导航和键盘焦点功能。

Summary by Sourcery

Bug Fixes:

  • Prevent notification close buttons from remaining visible after expanding a notification when the mouse moves outside the notification center area while focus is still active.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: re2zero

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 4, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds hover tracking for the entire notification center and threads that state through the view and notification items so that the close button only remains visible when either hovered or focused while the mouse is still within the notification center area.

Sequence diagram for mouse leave handling and close button visibility

sequenceDiagram
    actor User
    participant Mouse as MousePointer
    participant NotifyCenter
    participant HoverHandler as centerHoverHandler
    participant NotifyView
    participant NormalNotify
    participant Impl as ImplComponent

    User->>Mouse: Move inside notification center
    Mouse->>HoverHandler: hover enter
    HoverHandler-->>NotifyCenter: hovered = true
    NotifyCenter->>NotifyView: centerHovered = mouseInCenter(true)
    NotifyView->>NormalNotify: centerHovered = true
    NormalNotify->>Impl: parentHovered = impl.hovered || (activeFocus && centerHovered)
    Impl-->>User: Close button visible

    User->>Mouse: Move outside notification center
    Mouse->>HoverHandler: hover leave
    HoverHandler-->>NotifyCenter: hovered = false
    NotifyCenter->>NotifyView: centerHovered = mouseInCenter(false)
    NotifyView->>NormalNotify: centerHovered = false
    NormalNotify->>Impl: recompute parentHovered
    Impl-->>User: Close button hidden when only focus is active
Loading

Class diagram for updated notification center hover tracking

classDiagram
    class NotifyCenter {
        int maxViewHeight
        int stagingViewCount
        int viewCount
        bool mouseInCenter
        gotoStagingLast()
        gotoStagingFirst()
    }

    class HoverHandler_center {
        bool hovered
    }

    class NotifyView {
        alias notifyModel
        alias viewPanelShown
        real viewHeight
        int viewCount
        bool centerHovered
        gotoHeaderFirst()
        gotoHeaderLast()
    }

    class NotifyViewDelegate {
        NotifyModel notifyModel
        Item view
        bool centerHovered
        setting(var pos, var params)
    }

    class NormalNotify {
        int implicitWidth
        int implicitHeight
        bool centerHovered
        gotoNextItem()
        gotoPrevItem()
    }

    class ImplComponent {
        bool hovered
        bool strongInteractive
        string contentIcon
        int contentRowCount
        bool parentHovered
    }

    NotifyCenter --> HoverHandler_center : contains
    NotifyCenter --> NotifyView : contains
    NotifyView --> NotifyViewDelegate : uses
    NotifyViewDelegate --> NormalNotify : selects_as_delegate
    NotifyView --> NotifyModel : uses
    NotifyView --> Item : view
    NormalNotify --> ImplComponent : contains

    NotifyCenter : mouseInCenter = centerHoverHandler.hovered
    NotifyView : centerHovered from NotifyCenter.mouseInCenter
    NotifyViewDelegate : centerHovered required
    NormalNotify : centerHovered from NotifyView.centerHovered
    ImplComponent : parentHovered = impl.hovered || (root.activeFocus && root.centerHovered)
Loading

File-Level Changes

Change Details Files
Track whether the mouse is inside the notification center and expose it as a property.
  • Introduce a readonly mouseInCenter property on the NotifyCenter root that reflects the HoverHandler.hovered state.
  • Add a HoverHandler to the notificationCenter Item to capture hover for the full center region.
panels/notification/center/NotifyCenter.qml
Propagate the notification center hover state down to notification items.
  • Add a centerHovered property to NotifyView and wire it from the NotifyCenter mouseInCenter property.
  • Require and pass the centerHovered property through NotifyViewDelegate to NormalNotify.
  • Add a centerHovered property to NormalNotify to receive the propagated state.
panels/notification/center/NotifyView.qml
panels/notification/center/NotifyViewDelegate.qml
panels/notification/center/NormalNotify.qml
Gate focused-state hover behavior on whether the mouse is inside the notification center so the close button hides when the pointer leaves.
  • Change parentHovered on NormalNotify implementation to impl.hovered

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The same concept is referred to as mouseInCenter in NotifyCenter.qml and centerHovered in other components; consider standardizing on a single property name to make the data flow easier to follow.
  • You now gate parentHovered on root.activeFocus && root.centerHovered; if keyboard focus alone (with the mouse outside the center) should still visually indicate hover/close affordances for accessibility, you may want to revisit this condition or add a separate visual state for keyboard focus.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The same concept is referred to as `mouseInCenter` in `NotifyCenter.qml` and `centerHovered` in other components; consider standardizing on a single property name to make the data flow easier to follow.
- You now gate `parentHovered` on `root.activeFocus && root.centerHovered`; if keyboard focus alone (with the mouse outside the center) should still visually indicate hover/close affordances for accessibility, you may want to revisit this condition or add a separate visual state for keyboard focus.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@BLumia BLumia requested a review from 18202781743 February 4, 2026 03:02
Refactor Tab/Shift+Tab navigation logic to properly handle disabled buttons
and unify focus management across notification items.

改进通知中心键盘导航逻辑,正确处理禁用按钮的焦点跳转,
统一通知项和按钮的焦点管理。

Log: 改进通知中心键盘导航
PMS: BUG-347241
Influence: 使用键盘 Tab/Shift+Tab 浏览通知时,现在会正确跳过禁用的按钮,焦点顺序更符合预期;关闭按钮仅在键盘导航或鼠标悬停时显示,界面更简洁。
@deepin-ci-robot
Copy link

deepin pr auto review

这份代码修改主要是针对通知中心(Notification Center)中的键盘导航逻辑进行了重构,改进了Tab键和Shift+Tab键在通知项之间的切换行为,并优化了焦点管理和关闭按钮的显示逻辑。下面是对代码的详细审查和改进意见:

1. 语法逻辑审查

优点:

  • 代码逻辑清晰,将键盘导航的处理从Delegate统一移到了各个Notify组件内部,实现了更好的封装。
  • 新增了focusLastButton()方法,支持Shift+Tab反向导航,使焦点循环更加完整。
  • 使用shouldShowClose属性来区分鼠标悬停和键盘导航导致的焦点状态,逻辑合理。

潜在问题:

  1. GroupNotify.qml中的focusLastButton实现不完整

    function focusLastButton() {
        groupClearBtn.forceActiveFocus()
        return true
    }

    这个实现直接强制聚焦到清除按钮,没有检查按钮是否可用或可见,也没有考虑其他按钮的情况。建议改为:

    function focusLastButton() {
        if (groupClearBtn && groupClearBtn.enabled && groupClearBtn.visible) {
            groupClearBtn.forceActiveFocus()
            return true
        }
        // 尝试聚焦到其他按钮
        return false
    }
  2. NotifyAction.qml中的按钮焦点逻辑
    focusFirstButtonfocusLastButton中,对Loader.item的检查可能存在时序问题。如果Loader尚未完成加载,item可能为null。建议添加更多防护:

    if (secondActionLoader.status === Loader.Ready && secondActionLoader.item) {
        // ...
    }

2. 代码质量审查

优点:

  • 代码结构清晰,注释详细,特别是对键盘导航逻辑的说明。
  • 使用了统一的命名规范,如focusFirstButtonfocusLastButton等。

改进建议:

  1. 重复代码较多
    在多个Notify组件(GroupNotify、NormalNotify、OverlapNotify)中,键盘事件处理逻辑非常相似。建议提取为基类或混合(Mixin)组件,减少重复代码。

  2. 状态管理可以更集中
    shouldShowClosecenterHovered属性在多个组件中重复定义。可以考虑将这些状态提升到更高层级(如NotifyViewDelegate)统一管理。

  3. 魔法数字

    if (i === 1 && secondActionLoader.item) {
        // ...
    } else if (i > 1 && moreActionsLoader.item) {
        // ...
    }

    这里的数字1应该定义为常量,提高代码可读性。

3. 代码性能审查

优点:

  • 使用了Qt.callLater来延迟某些操作,避免阻塞UI线程。
  • 移除了不必要的Loader,减少了内存占用。

改进建议:

  1. 频繁的属性绑定
    parentHovered: impl.hovered || (root.activeFocus && (root.shouldShowClose || root.centerHovered))
    这个绑定在多个地方使用,且涉及多个属性变化。如果频繁触发,可能影响性能。建议考虑使用计算属性或缓存机制。

  2. 循环查找按钮

    for (let i = 0; i < actions.length; i++) {
        if (actions[i] && actions[i].enabled) return true
    }

    如果actions数组很大,这个循环可能会影响性能。建议在actions变化时预先计算可用按钮,而不是每次焦点变化时都重新查找。

4. 代码安全审查

优点:

  • 添加了hasEnabledAction属性来检查是否有可用的操作按钮,避免对不可用按钮进行操作。
  • actions[i]进行了null检查。

改进建议:

  1. 焦点安全
    在多个地方使用了forceActiveFocus(),但没有检查组件是否可见或启用。建议添加检查:

    if (button.visible && button.enabled) {
        button.forceActiveFocus()
    }
  2. 事件处理
    所有的键盘事件都设置了event.accepted = true,这可能会阻止其他组件处理这些事件。建议在必要时才接受事件:

    Keys.onTabPressed: function(event) {
        if (/* 成功处理了焦点 */) {
            event.accepted = true
        }
        // 否则不设置event.accepted,让事件继续传播
    }
  3. Loader状态检查
    在访问Loader.item之前,应该检查Loader的状态:

    if (loader.status === Loader.Ready && loader.item) {
        // 安全访问loader.item
    }

5. 其他建议

  1. 可访问性改进

    • 为键盘导航添加声音反馈或视觉提示,帮助用户了解当前焦点位置。
    • 考虑添加快捷键支持,如按Enter键执行默认操作。
  2. 测试建议

    • 测试边界情况:没有可用按钮时、所有按钮都禁用时、只有一个按钮时等。
    • 测试焦点循环:从最后一个通知按Tab是否能回到第一个通知。
    • 测试不同屏幕尺寸下的导航行为。
  3. 文档完善

    • 为新增的属性和方法添加更详细的文档说明。
    • 说明键盘导航的完整流程和规则。

总结

这次修改整体上改进了通知中心的键盘导航体验,代码质量较高。主要需要关注的是:

  1. 完善焦点管理中的边界情况处理
  2. 减少重复代码,提高代码复用性
  3. 增强对Loader和按钮状态的检查
  4. 优化性能敏感的操作

建议在合并前进行全面的键盘导航测试,特别是针对各种边界情况和不同通知类型的组合场景。

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to fix a bug where the close button remains visible after expanding a notification when the mouse leaves the notification center area. The fix introduces mouse position tracking through a centerHovered property and keyboard navigation state tracking via shouldShowClose to control when the close button should be displayed.

Changes:

  • Refactored Tab/Backtab navigation to use parent Control handlers instead of item-level handlers
  • Added centerHovered and shouldShowClose properties to track mouse and keyboard focus states
  • Improved focus management functions to properly handle disabled buttons and return success/failure status
  • Introduced focusFirstActionOnly() to avoid circular focus loops in OverlapNotify

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
NotifyItemContent.qml Refactored focus navigation logic with improved return values and added focusFirstActionOnly() method
NotifyAction.qml Enhanced button focus functions to handle disabled actions and return boolean success indicators
OverlapNotify.qml Added centerHovered/shouldShowClose properties and external clearButton reference for focus management
NotifyViewDelegate.qml Simplified delegate by removing item-level Tab handlers and passing centerHovered to child components
NormalNotify.qml Added centerHovered/shouldShowClose properties and moved Tab handlers to internal Control
GroupNotify.qml Added focusLastButton() function and moved Tab handlers to internal Control

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +179 to +180
// Try to focus first action button
if (overlapNotify.focusFirstButton()) {
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates a circular focus issue. When Tab is pressed on the clear button, it calls overlapNotify.focusFirstButton(), which will try to focus the clear button itself first (line 33-35 in OverlapNotify.qml), since clearButton is set to reference this same button (line 200). This means pressing Tab on the clear button will either focus itself again or do nothing. The Tab handler should instead call overlapNotify.focusFirstActionOnly() to skip the clear button and focus action buttons, or directly call gotoNextItem() if there are no action buttons.

Suggested change
// Try to focus first action button
if (overlapNotify.focusFirstButton()) {
// Try to focus first action button (skip clear button itself)
if (overlapNotify.focusFirstActionOnly && overlapNotify.focusFirstActionOnly()) {

Copilot uses AI. Check for mistakes.

Keys.onTabPressed: function(event) {
// Mark that this item got focus from Tab navigation
root.shouldShowClose = true
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shouldShowClose property is set to true here but is never reset to false anywhere in the codebase. This means once keyboard navigation is used (Tab/Shift+Tab), the close button will remain visible permanently even when the mouse leaves and the item loses focus. This contradicts the PR's stated goal of hiding the close button when the mouse leaves the notification center. Consider adding a mechanism to reset shouldShowClose to false when the item loses focus and the mouse is not hovering (e.g., using onActiveFocusChanged).

Copilot uses AI. Check for mistakes.
Control {
id: impl
anchors.fill: parent
focus: true
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shouldShowClose property is set to true here but is never reset to false anywhere in the codebase. This means once keyboard navigation is used (Tab/Shift+Tab), the close button will remain visible permanently even when the mouse leaves and the item loses focus. This contradicts the PR's stated goal of hiding the close button when the mouse leaves the notification center. Consider adding a mechanism to reset shouldShowClose to false when the item loses focus and the mouse is not hovering (e.g., using onActiveFocusChanged).

Suggested change
focus: true
focus: true
onActiveFocusChanged: {
if (!activeFocus && !root.centerHovered) {
root.shouldShowClose = false
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +179 to +184
// Try to focus first action button
if (overlapNotify.focusFirstButton()) {
event.accepted = true
return
}
// No enabled action buttons, go to next item
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is misleading. The code actually tries to focus all buttons in the notification (starting with the clear button itself), not just action buttons. The comment should be updated to reflect what actually happens, or the code should be changed to match the comment (e.g., by calling overlapNotify.notifyContent.focusFirstActionOnly() to skip the clear button).

Suggested change
// Try to focus first action button
if (overlapNotify.focusFirstButton()) {
event.accepted = true
return
}
// No enabled action buttons, go to next item
// Try to focus the next enabled button in this notification
if (overlapNotify.focusFirstButton()) {
event.accepted = true
return
}
// No enabled buttons to focus, go to next item

Copilot uses AI. Check for mistakes.
width: NotifyStyle.contentItem.width
activeFocusOnTab: false
z: index
centerHovered: root.centerHovered // Pass mouse hover state
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The property 'centerHovered' is referenced here but is never defined in the DelegateChooser. This will cause a runtime error. The 'centerHovered' property needs to be added to NotifyViewDelegate.qml (the DelegateChooser root) and then passed down from wherever NotifyViewDelegate is instantiated (likely NotifyView.qml). According to the PR description, this property should track the mouse hover state of the notification center to fix the close button visibility issue.

Copilot uses AI. Check for mistakes.
width: NotifyStyle.contentItem.width
activeFocusOnTab: false
z: index
centerHovered: root.centerHovered // Pass mouse hover state
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The property 'centerHovered' is referenced here but is never defined in the DelegateChooser. This will cause a runtime error. The 'centerHovered' property needs to be added to NotifyViewDelegate.qml (the DelegateChooser root) and then passed down from wherever NotifyViewDelegate is instantiated (likely NotifyView.qml). According to the PR description, this property should track the mouse hover state of the notification center to fix the close button visibility issue.

Copilot uses AI. Check for mistakes.
@deepin-bot
Copy link

deepin-bot bot commented Feb 5, 2026

TAG Bot

New tag: 2.0.29
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1436

property int count: 1
readonly property int overlapItemRadius: 12
property bool enableDismissed: true
property bool centerHovered: false // Mouse hover state from notification center
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

centerHovered 这个变量是不是没用到了,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants