Skip to content

refactor(notification): move bubble overlay logic to QML delegate#1565

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
mhduiy:bubbleani
Apr 23, 2026
Merged

refactor(notification): move bubble overlay logic to QML delegate#1565
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
mhduiy:bubbleani

Conversation

@mhduiy
Copy link
Copy Markdown
Contributor

@mhduiy mhduiy commented Apr 21, 2026

  1. Remove level property and overlay count tracking from BubbleItem and BubbleModel
  2. Introduce BubbleDelegate.qml with visual folding effects (scale, y-offset, opacity) based on index
  3. Simplify BubbleModel to use m_maxKeep limit instead of separate display/overlay counts
  4. Add remove and removeDisplaced transitions for smooth disappearance animations
  5. Consolidate NormalBubble.qml and OverlayBubble.qml into single Bubble.qml component
  6. Add delayed hide (500ms) in BubblePanel to allow QML animations to complete

Log: Refactor notification bubble system to handle overlay visual effects in QML instead of C++ model

refactor(notification): 将气泡折叠逻辑移至 QML 代理组件

  1. 移除 BubbleItem 和 BubbleModel 中的 level 属性及折叠计数追踪
  2. 新增 BubbleDelegate.qml,根据索引实现视觉折叠效果(缩放、Y 偏移、透明度)
  3. 简化 BubbleModel,使用 m_maxKeep 限制替代分离的显示/折叠计数
  4. 添加 remove 和 removeDisplaced 过渡动画实现平滑消失效果
  5. 合并 NormalBubble.qml 和 OverlayBubble.qml 为单一 Bubble.qml 组件
  6. BubblePanel 添加 500ms 延迟隐藏,确保 QML 动画播放完成

Log: 重构通知气泡系统,将折叠视觉效果从 C++ 模型移至 QML 处理

PMS: BUG-355029

Summary by Sourcery

Refactor the notification bubble system so that overlay/folding behavior and animations are handled in QML delegates instead of the C++ model, while simplifying bubble count management.

New Features:

  • Introduce a BubbleDelegate QML component to render notification bubbles with folding, scaling, offset, and opacity effects based on index.
  • Unify NormalBubble and OverlayBubble into a single Bubble QML component based on NotifyItemContent.
  • Add QML transitions for adding, removing, and displacing bubbles to animate their appearance and disappearance.

Bug Fixes:

  • Prevent abrupt disappearance of the bubble panel by delaying hide when the last bubble is removed, allowing animations to finish.

Enhancements:

  • Simplify BubbleModel by removing level/overlay tracking, exposing a bubbleCount role, and using a single max-keep limit for stored items.
  • Adjust layout and sizing in the main notification window to use the ListView content height and updated anchoring for the bubble view.
  • Remove now-unnecessary level state and overlay logic from BubbleItem in favor of purely visual handling in QML.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 21, 2026

Reviewer's Guide

Refactors the notification bubble system so that overlay/folding visuals and disappearance animations are handled entirely in QML delegates, while the C++ model becomes a simple list with a max-keep limit and updated roles, and the QML UI is updated to use a unified Bubble component and smoother panel visibility behavior.

Sequence diagram for bubble removal and panel hide with QML transitions

sequenceDiagram
    actor User
    participant Applet
    participant BubblePanel
    participant BubbleModel
    participant BubbleView as QML_BubbleView_ListView
    participant BubbleDelegate

    User->>BubbleDelegate: Dismiss or close bubble
    BubbleDelegate->>Applet: close(bubble.index, NotifyItem.Dismissed or Closed)
    Applet->>BubblePanel: onNotificationStateChanged(id, processedType)
    BubblePanel->>BubbleModel: remove(index)
    BubbleModel->>BubbleModel: takeAt(index), deleteLater()
    BubbleModel->>BubbleView: model changed (row removed)
    BubbleView->>BubbleDelegate: trigger remove and removeDisplaced transitions
    BubbleDelegate->>BubbleDelegate: play opacity,y,scale animations

    BubbleModel-->>BubblePanel: bubble count changed (model empty)
    BubblePanel->>BubblePanel: onBubbleCountChanged()
    alt model is empty
        BubblePanel->>BubblePanel: QTimer::singleShot(500ms)
        Note over BubbleDelegate,BubblePanel: remove transitions continue during delay
        BubblePanel-->>BubblePanel: after 500ms, check items().isEmpty()
        BubblePanel->>BubblePanel: setVisible(false)
    else model not empty
        BubblePanel->>BubblePanel: setVisible(enabled())
    end
Loading

Updated class diagram for notification bubble model and item

classDiagram
    class BubbleItem {
        +NotifyEntity m_entity
        -int m_urgency
        -QString m_timeTip
        -bool m_enablePreview
        +QString appName() const
        +QString body() const
        +QString summary() const
        +QString appIcon() const
        +QString bodyImagePath() const
        +int urgency() const
        +QVariantList actions() const
        +void updateActions()
        +QString timeTip() const
        +void setTimeTip(QString timeTip)
        +bool isValid() const
        +void timeTipChanged()
    }

    class BubbleModel {
        -QTimer* m_updateTimeTipTimer
        -QList~BubbleItem*~ m_bubbles
        -int m_maxKeep
        -int m_contentRowCount
        +BubbleModel(QObject* parent)
        +int rowCount(QModelIndex parent) const
        +QVariant data(QModelIndex index, int role) const
        +QHash~int, QByteArray~ roleNames() const
        +int displayRowCount() const
        +void push(BubbleItem* bubble)
        +void remove(int index)
        +void remove(const BubbleItem* bubble)
        +void clear()
        +void clearInvalidBubbles()
        -void updateBubbleCount(int count)
        -int replaceBubbleIndex(const BubbleItem* bubble) const
        -void updateBubbleTimeTip()
        -void updateContentRowCount(int rowCount)
    }

    class BubblePanel {
        -BubbleModel* m_bubbles
        +void onBubbleCountChanged()
        +void addBubble(qint64 id)
        +void onNotificationStateChanged(qint64 id, int processedType)
        +void setVisible(bool visible)
        +bool enabled() const
    }

    BubbleModel "1" --> "*" BubbleItem : owns
    BubblePanel "1" --> "1" BubbleModel : uses
Loading

File-Level Changes

Change Details Files
Simplify BubbleModel to a flat list with a max-keep limit and updated roles, removing overlay/level logic from C++.
  • Replace BubbleMaxCount/OverlayMaxCount with m_maxKeep, initialized from NotifySetting::bubbleCount() + 2.
  • On push(), drop the last bubble when exceeding m_maxKeep instead of managing display/overlay rows separately.
  • Remove level and overlayCount roles and logic, add BubbleCount role returning NotifySetting::bubbleCount().
  • Make displayRowCount() return full m_bubbles count and simplify updateBubbleCount() to only adjust m_maxKeep and emit BubbleCount changes.
  • Delete updateLevel() and all associated calls, and remove per-item level state from BubbleItem.
panels/notification/bubble/bubblemodel.cpp
panels/notification/bubble/bubblemodel.h
panels/notification/bubble/bubbleitem.cpp
panels/notification/bubble/bubbleitem.h
Move bubble folding/overlay visual behavior into a new QML delegate and switch to a unified Bubble component.
  • Add BubbleDelegate.qml that wraps Bubble, computes a realIndex, and applies y-offset, scale, and opacity based on index/maxCount, with animated Behaviors.
  • Change the ListView delegate in main.qml from Bubble to BubbleDelegate, passing model.bubbleCount as maxCount.
  • Replace NormalBubble.qml and OverlayBubble.qml with a single Bubble.qml that directly uses NotifyItemContent and calls Applet.close/invokeAction on user interactions.
panels/notification/bubble/package/BubbleDelegate.qml
panels/notification/bubble/package/main.qml
panels/notification/bubble/package/Bubble.qml
panels/notification/bubble/package/NormalBubble.qml
panels/notification/bubble/package/OverlayBubble.qml
Improve ListView layout/animations and panel visibility timing to better support QML-driven transitions.
  • Use bubbleView.contentHeight instead of height for the window preferredHeight and adjust bubbleView anchoring to top with new margins.
  • Extend addDisplaced transition to animate y, introduce remove and removeDisplaced transitions to animate opacity (and y) on removal.
  • Add a 500ms delayed hide in BubblePanel when the model becomes empty so that QML remove transitions can complete before the panel disappears.
  • Add an invisible Rectangle debug overlay (currently unused) in main.qml for layout inspection.
panels/notification/bubble/package/main.qml
panels/notification/bubble/bubblepanel.cpp

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
Copy Markdown

@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 overlay retention logic is split between C++ (m_maxKeep = bubbleCount + 2) and QML (opacity/levelsFolded based on maxCount and hard-coded 2/3), which makes behavior harder to reason about; consider centralizing these constants or exposing a single role from the model so QML doesn’t have to duplicate the ‘+2’ and fold-depth assumptions.
  • BubbleDelegate.qml’s realIndex calculation relies on ListView.view.count and index potentially being negative, which couples the delegate tightly to ListView internals; if you only target ListView, you can likely drop the negative-index adjustment and use index directly, or otherwise document this assumption to avoid fragile reuse.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The overlay retention logic is split between C++ (m_maxKeep = bubbleCount + 2) and QML (opacity/levelsFolded based on maxCount and hard-coded 2/3), which makes behavior harder to reason about; consider centralizing these constants or exposing a single role from the model so QML doesn’t have to duplicate the ‘+2’ and fold-depth assumptions.
- BubbleDelegate.qml’s realIndex calculation relies on ListView.view.count and index potentially being negative, which couples the delegate tightly to ListView internals; if you only target ListView, you can likely drop the negative-index adjustment and use index directly, or otherwise document this assumption to avoid fragile reuse.

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.

@mhduiy mhduiy force-pushed the bubbleani branch 6 times, most recently from 9f122b6 to 0c4074e Compare April 23, 2026 07:57
18202781743
18202781743 previously approved these changes Apr 23, 2026
1. Remove level property and overlay count tracking from BubbleItem and BubbleModel
2. Introduce BubbleDelegate.qml with visual folding effects (scale, y-offset, opacity) based on index
3. Simplify BubbleModel to use m_maxKeep limit instead of separate display/overlay counts
4. Add remove and removeDisplaced transitions for smooth disappearance animations
5. Consolidate NormalBubble.qml and OverlayBubble.qml into single Bubble.qml component
6. Add delayed hide (500ms) in BubblePanel to allow QML animations to complete

Log: Refactor notification bubble system to handle overlay visual effects in QML instead of C++ model

refactor(notification): 将气泡折叠逻辑移至 QML 代理组件

1. 移除 BubbleItem 和 BubbleModel 中的 level 属性及折叠计数追踪
2. 新增 BubbleDelegate.qml,根据索引实现视觉折叠效果(缩放、Y 偏移、透明度)
3. 简化 BubbleModel,使用 m_maxKeep 限制替代分离的显示/折叠计数
4. 添加 remove 和 removeDisplaced 过渡动画实现平滑消失效果
5. 合并 NormalBubble.qml 和 OverlayBubble.qml 为单一 Bubble.qml 组件
6. BubblePanel 添加 500ms 延迟隐藏,确保 QML 动画播放完成

Log: 重构通知气泡系统,将折叠视觉效果从 C++ 模型移至 QML 处理

PMS: BUG-355029
@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, mhduiy

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

@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这份代码 diff 主要实现了三个方面的功能改进:

  1. 为 LayerShell 添加了输入区域(Input Region)控制功能,支持 Wayland 和 X11
  2. 重构了通知气泡(Bubble)的显示逻辑,使用更平滑的动画和折叠效果
  3. 清理了一些不再使用的代码和属性

以下是详细的代码审查意见:

1. 语法逻辑审查

1.1 输入区域实现

优点:

  • X11 和 Wayland 两种后端都实现了输入区域控制
  • 正确处理了设备像素比例(scaleFactor)的转换
  • 使用了 Qt 的信号槽机制进行属性变更通知

问题:

  1. x11dlayershellemulation.cpp 中,onInputRegionChanged() 函数缺少错误处理:
// 建议添加 XCB 错误处理
xcb_generic_error_t *error = nullptr;
xcb_shape_rectangles(...);
if (error) {
    qWarning() << "Failed to set input region:" << error->error_code;
    free(error);
}
  1. qwaylandlayershellsurface.cpp 中,applyInputRegion lambda 捕获了 thiswindow,但没有检查 window 的有效性:
// 建议添加检查
auto applyInputRegion = [this, window]() {
    if (!window || !window->window()) return;
    window->window()->setMask(m_dlayerShellWindow->inputRegion());
    window->waylandSurface()->commit();
};

1.2 通知气泡模型

优点:

  • 使用 QQueue 管理待处理的气泡,实现平滑插入
  • 正确实现了 QAbstractListModel 的接口

问题:

  1. BubbleModel::push() 中的逻辑可能导致内存泄漏:
// 如果在定时器触发前对象被删除,pendingBubbles 中的指针会悬空
// 建议在析构函数中已经处理了,但可以更明确
BubbleModel::~BubbleModel()
{
    if (m_processPendingTimer) {
        m_processPendingTimer->stop();
    }
    qDeleteAll(m_pendingBubbles);  // 已有
    m_pendingBubbles.clear();
    qDeleteAll(m_bubbles);  // 已有
    m_bubbles.clear();
}
  1. BubbleModel::insertBubble() 中的边界检查可以更严格:
// 建议添加空指针检查
void BubbleModel::insertBubble(BubbleItem *bubble)
{
    if (!bubble) return;  // 添加空指针检查
    // ... 其余代码
}

2. 代码质量审查

2.1 代码组织

  • 优点:功能模块划分清晰,X11 和 Wayland 实现分离
  • 问题BubbleDelegate.qml 中的魔法数字较多:
// 建议定义为属性
property int maxCount: 3
property int spacing: 10
property int peekAmount: 8
property int maxFoldedLevels: 3

2.2 注释和文档

  • 问题:部分新增函数缺少注释说明,例如 setInputRegionRect
/**
 * @brief 设置矩形输入区域
 * @param x 矩形左上角X坐标
 * @param y 矩形左上角Y坐标
 * @param width 矩形宽度
 * @param height 矩形高度
 */
void DLayerShellWindow::setInputRegionRect(int x, int y, int width, int height)

2.3 代码重复

  • Bubble.qml 和已删除的 NormalBubble.qml 功能相同,建议保留 NormalBubble.qml 作为独立组件,Bubble.qml 作为包装器

3. 代码性能审查

3.1 输入区域更新

  • 问题:在 main.qml 中,updateInputRegion() 可能在动画过程中被频繁调用:
// 建议使用防抖(debounce)机制
function updateInputRegion() {
    if (updateInputRegionTimer.running) return;
    updateInputRegionTimer.start();
}
Timer {
    id: updateInputRegionTimer
    interval: 16  // ~60fps
    onTriggered: {
        root.DLayerShellWindow.setInputRegionRect(...);
    }
}

3.2 模型更新

  • 优点:使用定时器批量处理待插入的气泡,避免频繁更新模型
  • 问题clearInvalidBubbles() 可能导致多次模型更新:
// 建议使用 beginResetModel/endResetModel 批量处理
void BubbleModel::clearInvalidBubbles()
{
    if (m_bubbles.isEmpty()) return;
    
    beginResetModel();
    // 批量删除无效气泡
    m_bubbles.erase(std::remove_if(m_bubbles.begin(), m_bubbles.end(), 
        [](BubbleItem *item) { return !item->isValid(); }), 
        m_bubbles.end());
    endResetModel();
}

4. 代码安全审查

4.1 输入验证

  • 问题setInputRegionRect 缺少参数验证:
void DLayerShellWindow::setInputRegionRect(int x, int y, int width, int height)
{
    // 添加参数验证
    if (width <= 0 || height <= 0) {
        qWarning() << "Invalid rectangle dimensions:" << width << height;
        return;
    }
    setInputRegion(QRegion(x, y, width, height));
}

4.2 内存安全

  • 问题BubbleModel 中的指针管理需要更谨慎:
// 建议使用 QSharedPointer 或 QScopedPointer
QList<QSharedPointer<BubbleItem>> m_bubbles;
QQueue<QSharedPointer<BubbleItem>> m_pendingBubbles;

4.3 线程安全

  • 问题:X11 和 Wayland 的输入区域更新都在主线程执行,如果窗口属性频繁变化可能导致阻塞:
// 建议考虑使用 QtConcurrent 处理耗时操作
QtConcurrent::run([this]() {
    // XCB 调用
    QMetaObject::invokeMethod(this, [this]() {
        // 更新 UI
    }, Qt::QueuedConnection);
});

5. 其他建议

  1. 版权年份更新:多个文件的版权年份从 2023/2024 更新到 2026,建议确认是否合理

  2. 测试建议

    • 添加输入区域边界的单元测试
    • 测试高 DPI 显示器上的输入区域准确性
    • 测试快速添加/删除通知气泡时的内存使用情况
  3. 性能监控

    • 添加性能监控点,记录输入区域更新和模型更新的耗时
    • 监控大量通知时的内存使用情况
  4. 向后兼容性

    • 考虑保留 OverlayBubble.qml 作为可选组件,便于回退

总体而言,这次更新实现了重要的功能改进,但在错误处理、性能优化和代码健壮性方面还有提升空间。建议在合并前添加适当的错误处理和单元测试。

@mhduiy
Copy link
Copy Markdown
Contributor Author

mhduiy commented Apr 23, 2026

/forcemerge

@deepin-bot
Copy link
Copy Markdown

deepin-bot Bot commented Apr 23, 2026

This pr force merged! (status: blocked)

@deepin-bot deepin-bot Bot merged commit 79e063b into linuxdeepin:master Apr 23, 2026
9 of 12 checks passed
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