Skip to content

Tray load animation#1555

Open
BLumia wants to merge 2 commits intolinuxdeepin:masterfrom
BLumia:tray-load-animation
Open

Tray load animation#1555
BLumia wants to merge 2 commits intolinuxdeepin:masterfrom
BLumia:tray-load-animation

Conversation

@BLumia
Copy link
Copy Markdown
Member

@BLumia BLumia commented Apr 15, 2026

Summary by Sourcery

Introduce a startup phase for the dock tray to control initial visibility and animations of tray items.

New Features:

  • Add a startup phase state to the tray sort order model, exposed to QML, to manage initial tray item visibility.
  • Implement startup-specific hidden states and entrance animations for tray and stashed items in QML.

Enhancements:

  • Batch tray surface updates during startup using a timer to avoid flicker and provide a smoother initial tray load animation.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 15, 2026

Reviewer's Guide

Implements a startup phase for tray items with a timer-driven end, exposing it via TraySortOrderModel and using new QML states and transitions to hide all tray items during startup and then animate them into view without layout flicker.

Sequence diagram for tray startup phase and load animation

sequenceDiagram
    actor User
    participant Dock
    participant Model as TraySortOrderModel
    participant StartupTimer as QTimer
    participant TrayItemQML as TrayItemPositioner
    participant StashedItemQML as StashedItemPositioner

    User->>Dock: Start desktop session
    Dock->>Model: Construct TraySortOrderModel()
    activate Model
    Model->>StartupTimer: new QTimer(this)
    StartupTimer-->>Model: configured singleShot(500ms)
    Model->>TrayItemQML: Q_PROPERTY startupPhase (true)
    Model->>StashedItemQML: Q_PROPERTY startupPhase (true)

    TrayItemQML->>TrayItemQML: itemVisible evaluates to false
    StashedItemQML->>StashedItemQML: itemVisible evaluates to false
    TrayItemQML->>TrayItemQML: enter state startup-hidden
    StashedItemQML->>StashedItemQML: enter state startup-hidden

    loop surfaces appear during startup
        Dock->>Model: onAvailableSurfacesChanged()
        Model->>Model: updateVisualIndexes()
        Model->>Model: saveDataToDConfig()
        alt startupPhase is true
            Model->>StartupTimer: start() (reset 500ms)
        end
    end

    StartupTimer-->>Model: timeout()
    Model->>Model: setStartupPhase(false)
    Model-->>TrayItemQML: startupPhaseChanged(false)
    Model-->>StashedItemQML: startupPhaseChanged(false)

    TrayItemQML->>TrayItemQML: leave startup-hidden\nresolve itemVisible
    StashedItemQML->>StashedItemQML: leave startup-hidden\nresolve itemVisible

    TrayItemQML->>TrayItemQML: Transition startup-hidden -> item-visible\nrun opacity/scale animations
    StashedItemQML->>StashedItemQML: Transition startup-hidden -> item-visible or item-invisible\nrun opacity animation
Loading

Class diagram for updated TraySortOrderModel startup phase

classDiagram
    class TraySortOrderModel {
        %% Q_PROPERTIES
        bool isCollapsing
        bool actionsAlwaysVisible
        bool isUpdating
        bool startupPhase
        QList~QVariantMap~ availableSurfaces
        QString stagedSurfaceId
        int stagedVisualIndex

        %% Methods
        +TraySortOrderModel(QObject *parent)
        +void stageDropPosition(QString surfaceId, int visualIndex)
        +void commitStagedDrop()
        +void clearStagedDrop()
        +bool startupPhase() const
        +void setStartupPhase(bool phase)

        %% Signals
        void collapsedChanged(bool collapsed)
        void isCollapsingChanged(bool collapsing)
        void actionsAlwaysVisibleChanged(bool visible)
        void isUpdatingChanged(bool updating)
        void startupPhaseChanged(bool phase)
        void visualItemCountChanged(int count)
        void availableSurfacesChanged(QList~QVariantMap~ surfaces)
        void stagedDropChanged()

        %% Internal state
        bool m_collapsed
        bool m_isCollapsing
        bool m_actionsAlwaysVisible
        bool m_isUpdating
        bool m_startupPhase
        std::unique_ptr~Dtk::Core::DConfig~ m_dconfig
        QTimer * m_startupTimer
        QList~QVariantMap~ m_availableSurfaces
    }

    class QTimer {
        +void setSingleShot(bool singleShot)
        +void setInterval(int msec)
        +void start()
        +void timeout()
    }

    TraySortOrderModel --> QTimer : uses m_startupTimer
Loading

State diagram for tray item visibility with startup phase

stateDiagram-v2
    [*] --> startup_hidden

    state startup_hidden {
    }

    state item_visible {
    }

    state item_invisible {
    }

    startup_hidden --> item_visible: TraySortOrderModel.startupPhase becomes false
    startup_hidden --> item_invisible: TraySortOrderModel.startupPhase becomes false and itemVisible false

    item_visible --> item_invisible: itemVisible becomes false (e.g. isUpdating true or other rules)
    item_invisible --> item_visible: itemVisible becomes true

    note "Logic (TrayItemPositioner / StashedItemPositioner):\n- If TraySortOrderModel.startupPhase is true: itemVisible = false, force startup_hidden state.\n- After startupPhase becomes false, normal visibility rules apply (isUpdating, plugin visibility, etc.).\n- Transition from startup_hidden triggers entry animations into item_visible or item_invisible." as N1
Loading

File-Level Changes

Change Details Files
Add startup phase state and timer to TraySortOrderModel to control when tray items become visible.
  • Introduce startupPhase Q_PROPERTY with getter, setter, and change signal to expose startup state to QML.
  • Initialize m_startupPhase to true and add m_startupTimer to end startup after 500ms of inactivity in available surfaces.
  • Update onAvailableSurfacesChanged to restart the startup timer during startup, batching surface changes before revealing items.
panels/dock/tray/traysortordermodel.h
panels/dock/tray/traysortordermodel.cpp
Hide tray items during startup and add dedicated startup-hidden QML state with entry animation when startup ends.
  • Extend itemVisible logic in TrayItemPositioner and StashedItemPositioner to return false while startupPhase is true, avoiding animations/layout changes during startup.
  • Add startup-hidden state in both positioner QMLs that forces opacity 0, smaller scale (for tray items), and visible:false during startup.
  • Define transitions from startup-hidden to normal states that animate opacity (and scale for tray items) over 300ms for a smooth tray load effect once startup ends.
panels/dock/tray/package/TrayItemPositioner.qml
panels/dock/tray/package/StashedItemPositioner.qml

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

@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这段代码主要实现了在系统托盘启动阶段隐藏所有托盘项,并在启动完成后通过动画显示它们的功能,以避免启动过程中的闪烁和布局抖动。以下是对代码的详细审查和改进意见:

1. 语法逻辑

StashedItemPositioner.qml & TrayItemPositioner.qml

  • 逻辑一致性:代码逻辑基本正确。通过引入 startupPhase 状态,在启动期间强制 itemVisiblefalse,并配合 StateTransition 实现了从隐藏到显示的过渡。
  • 状态切换:在 Transition 中使用了 from: "startup-hidden"to: "*",这意味着当 startupPhase 变为 false 时,无论下一个状态是什么(通常是 itemVisibletrue 的状态),都会触发这个过渡动画。这是合理的。
  • 潜在的逻辑风险:在 TrayItemPositioner.qml 中,itemVisible 的计算逻辑包含了 startupPhaseisUpdating。如果 startupPhase 结束时 isUpdating 仍为 trueitemVisible 将保持 false。此时,State 中的 when: root.itemVisible 条件不满足,组件可能会停留在 opacity: 0.0 的状态(或者 startup-hidden 状态,因为 when 不再匹配)。虽然逻辑上这符合“更新时不显示”的需求,但视觉上可能会显得卡顿。建议确认 startupPhase 结束时 isUpdating 的状态,或者确保 isUpdatingfalse 时才结束 startupPhase

TraySortOrderModel.cpp

  • 初始化m_startupPhase 初始化为 truem_startupTimer 初始化为 500ms 单次触发。逻辑正确。
  • 定时器逻辑:在 onAvailableSurfacesChanged 中重置定时器。这意味着只要在 500ms 内有新的 surface 变化,启动阶段就会延长。这是一种有效的防抖/批处理策略。
  • Setter 检查setStartupPhase 中进行了 if (m_startupPhase == phase) return; 检查,防止重复触发信号,这是良好的实践。

2. 代码质量

  • 注释:QML 代码中添加了清晰的注释(如 // Startup phase: hide all items...),提高了可读性。
  • 魔法数字500 (ms) 和 300 (ms) 等硬编码的时间值最好定义为常量,以便于统一管理和调整。
    • 建议:在 C++ 类中定义 static constexpr int STARTUP_DELAY_MS = 500;,在 QML 中可以使用定义好的属性或常量。
  • 内存管理m_startupTimer 使用了 new QTimer(this),利用了 QObject 的父子对象机制,无需手动 delete,内存管理是安全的。
  • 信号命名startupPhaseChanged(bool) 符合 Qt 的命名规范。

3. 代码性能

  • 动画开销:引入了 ParallelAnimation(透明度 + 缩放),这比单一动画消耗略多,但对于启动这种低频操作来说,性能影响可以忽略不计,且能带来更好的视觉效果。
  • 属性绑定itemVisible 是一个带有 if 语句的属性绑定。这在 QML 中是高效的,因为绑定引擎只会在依赖项(startupPhase, isUpdating)改变时重新计算。
  • 定时器开销:使用 QTimer 进行延迟处理是非常轻量级的,不会造成性能问题。

4. 代码安全

  • 空指针检查:在 onAvailableSurfacesChanged 中检查了 if (m_startupPhase && m_startupTimer)。虽然 m_startupTimer 在构造函数中初始化,理论上不会为空,但这种防御性编程是好的,增加了代码的健壮性。
  • 状态一致性:通过属性绑定和信号机制确保了 C++ 端 (m_startupPhase) 和 QML 端 (DDT.TraySortOrderModel.startupPhase) 的状态同步,逻辑上是安全的。

5. 改进建议

  1. 常量定义
    建议将 500ms300ms 提取为常量。

    • C++ (traysortordermodel.h):
      static constexpr int STARTUP_TIMEOUT_MS = 500;
    • C++ (traysortordermodel.cpp):
      m_startupTimer->setInterval(STARTUP_TIMEOUT_MS);
    • QML: 虽然很难直接引用 C++ 的 constexpr,但可以在 C++ 中暴露一个 Q_PROPERTY(int startupAnimationDuration READ ...) 或者在 QML 文件顶部定义 pragma ComponentBehavior: Bound(如果适用)或直接使用文档注释说明。对于 QML 中的 300ms,可以保持现状或定义一个根级的 property int animationDuration: 300
  2. 状态管理优化
    TrayItemPositioner.qml 中,startup-hidden 状态显式设置了 visible: false。当 startupPhase 变为 false 时,Transition 会立即设置 visible: true。这很好。
    然而,如果 itemVisible 的逻辑导致组件在启动结束后应该保持不可见(例如 isUpdating 为真),当前的 Transition 仍然会强制 visible: true 并执行淡入动画,然后紧接着可能因为状态切换回不可见而执行消失动画。

    • 建议:虽然当前的逻辑大概率是 startupPhase 结束时 isUpdating 已经结束,但为了绝对严谨,可以在 Transitionto 属性中更具体一点,或者确保 setStartupPhase(false) 只在系统完全稳定时调用。目前的实现看起来是为了"启动展示",所以假设启动后即展示是合理的。
  3. 日志输出
    qDebug() << "Startup phase ended..." 是有用的调试信息。在发布版本中,建议使用 qCDebug 并配合分类日志,以便在不重新编译的情况下关闭这些日志。

  4. TrayItemPositioner.qml 的动画曲线
    使用了 Easing.OutBack 用于缩放动画。这会产生一个轻微的"回弹"效果。

    • 审查OutBack 效果比较活泼,适合启动时的强调效果。但如果系统启动非常快,这种回弹可能会显得突兀。建议保留,但需在实际设备上测试观感。

总结

这段代码整体质量很高,逻辑清晰,有效地解决了启动时的闪烁问题。

  • 优点:状态机设计合理,使用了防抖定时器,内存管理安全,注释清晰。
  • 需要关注:主要是硬编码的数值和极端情况下的状态切换(如启动结束时正好在更新)。

修改后的代码片段建议(针对常量定义):

// traysortordermodel.h
class TraySortOrderModel : public QStandardItemModel
{
    Q_OBJECT
    // ... 其他属性 ...
public:
    static constexpr int STARTUP_TIMEOUT_MS = 500; // 定义常量
    // ...
};

// traysortordermodel.cpp
TraySortOrderModel::TraySortOrderModel(QObject *parent)
    : QStandardItemModel(parent)
{
    // ...
    m_startupTimer->setInterval(STARTUP_TIMEOUT_MS); // 使用常量
    // ...
}

总体而言,这是一个可以合并的改动,只需在实际测试中关注启动速度较快时的动画流畅度。

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 found 3 issues, and left some high level feedback:

  • In both TrayItemPositioner.qml and StashedItemPositioner.qml, itemVisible returns false during startupPhase while you also have a startup-hidden state conditioned on startupPhase; since the item-invisible state (when: !root.itemVisible) is declared after startup-hidden, it will win when both conditions are true, so you likely need to reorder the states or decouple itemVisible from startupPhase to ensure the startup animation state is actually used.
  • Consider explicitly resetting startupPhase or starting the m_startupTimer in the model constructor or an initialization path; as written, if no new surfaces arrive, the model will remain in startupPhase indefinitely and tray items will stay hidden.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In both TrayItemPositioner.qml and StashedItemPositioner.qml, `itemVisible` returns false during `startupPhase` while you also have a `startup-hidden` state conditioned on `startupPhase`; since the `item-invisible` state (`when: !root.itemVisible`) is declared after `startup-hidden`, it will win when both conditions are true, so you likely need to reorder the states or decouple `itemVisible` from `startupPhase` to ensure the startup animation state is actually used.
- Consider explicitly resetting `startupPhase` or starting the `m_startupTimer` in the model constructor or an initialization path; as written, if no new surfaces arrive, the model will remain in `startupPhase` indefinitely and tray items will stay hidden.

## Individual Comments

### Comment 1
<location path="panels/dock/tray/traysortordermodel.cpp" line_range="68-71" />
<code_context>
         updateVisualIndexes();
     });
+    
+    // Startup phase timer: end startup phase after 500ms of no new surfaces
+    m_startupTimer = new QTimer(this);
+    m_startupTimer->setSingleShot(true);
+    m_startupTimer->setInterval(500);
+    connect(m_startupTimer, &QTimer::timeout, this, [this](){
+        if (m_startupPhase) {
</code_context>
<issue_to_address>
**issue:** Consider a fallback to end startupPhase even when no surfaces ever appear.

Since the timer only starts in onAvailableSurfacesChanged, m_startupPhase never ends if no tray surfaces ever appear and QML will keep items hidden indefinitely. Consider starting the timer once in the constructor (or adding an initial one-shot) so startupPhase finishes even when there are no surfaces.
</issue_to_address>

### Comment 2
<location path="panels/dock/tray/package/TrayItemPositioner.qml" line_range="83" />
<code_context>
         }
     ]
     transitions: [
+        Transition {
+            from: "startup-hidden"
+            to: "*"
</code_context>
<issue_to_address>
**issue (bug_risk):** Transition from "startup-hidden" to "*" may override the dedicated "item-invisible" transition.

Since `from: "startup-hidden"; to: "*"` matches all targets, it will also apply to `startup-hidden -> item-invisible`. Declared before the specific `to: "item-invisible"` transition, it will likely override it, causing elements that should stay hidden to briefly animate in. Consider narrowing this to a visible state only (e.g. `to: "item-visible"`) or reordering/adjusting conditions so `startup-hidden -> item-invisible` uses the non-showing transition.
</issue_to_address>

### Comment 3
<location path="panels/dock/tray/package/StashedItemPositioner.qml" line_range="48" />
<code_context>
         }
     ]
     transitions: [
+        Transition {
+            from: "startup-hidden"
+            to: "*"
</code_context>
<issue_to_address>
**issue (bug_risk):** Same transition precedence issue for stashed items as in TrayItemPositioner.

In this case `from: "startup-hidden"; to: "*"` also matches `startup-hidden -> item-invisible`, so the show animation runs even when the item should stay hidden. Restrict the `to` state (e.g., only the visible state) or adjust transition order to prevent animations for items that remain invisible after startup.
</issue_to_address>

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.

Comment on lines +68 to +71
// Startup phase timer: end startup phase after 500ms of no new surfaces
m_startupTimer = new QTimer(this);
m_startupTimer->setSingleShot(true);
m_startupTimer->setInterval(500);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Consider a fallback to end startupPhase even when no surfaces ever appear.

Since the timer only starts in onAvailableSurfacesChanged, m_startupPhase never ends if no tray surfaces ever appear and QML will keep items hidden indefinitely. Consider starting the timer once in the constructor (or adding an initial one-shot) so startupPhase finishes even when there are no surfaces.

}
]
transitions: [
Transition {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Transition from "startup-hidden" to "*" may override the dedicated "item-invisible" transition.

Since from: "startup-hidden"; to: "*" matches all targets, it will also apply to startup-hidden -> item-invisible. Declared before the specific to: "item-invisible" transition, it will likely override it, causing elements that should stay hidden to briefly animate in. Consider narrowing this to a visible state only (e.g. to: "item-visible") or reordering/adjusting conditions so startup-hidden -> item-invisible uses the non-showing transition.

}
]
transitions: [
Transition {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Same transition precedence issue for stashed items as in TrayItemPositioner.

In this case from: "startup-hidden"; to: "*" also matches startup-hidden -> item-invisible, so the show animation runs even when the item should stay hidden. Restrict the to state (e.g., only the visible state) or adjust transition order to prevent animations for items that remain invisible after startup.

@BLumia
Copy link
Copy Markdown
Member Author

BLumia commented Apr 15, 2026

备注:暂时测试效果用,无视commitmsg和许可协议合规检查即可。

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

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