Skip to content

fix(windowed): optimize sort mode switch performance#783

Open
Ivy233 wants to merge 1 commit into
linuxdeepin:masterfrom
Ivy233:fix/optimize-sort-switch-perf
Open

fix(windowed): optimize sort mode switch performance#783
Ivy233 wants to merge 1 commit into
linuxdeepin:masterfrom
Ivy233:fix/optimize-sort-switch-perf

Conversation

@Ivy233

@Ivy233 Ivy233 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
  1. Replace Loader with dual-view visible switching in AppList.qml to avoid costly component destruction/creation on each sort mode toggle
  2. Both AppListView and FreeSortListView are now always instantiated; switching is done via the visible property, reducing switch time from ~120ms to ~2ms
  3. Replace beginResetModel/endResetModel with setDynamicSortFilter toggle in CategorizedSortProxyModel::setCategoryType to avoid redundant sort triggered by setSortRole
  4. The new approach uses layoutAboutToBeChanged/layoutChanged (emitted internally by d->sort() via setDynamicSortFilter(true)) instead of modelReset, so existing delegates are moved rather than destroyed and recreated
  5. Extra memory cost is ~60-80KB for retaining hidden view delegates, which is negligible (< 0.03% of process RSS)

Log: Optimized windowed launcher sort mode switch from ~1000ms to ~18ms by replacing Loader with visible switching and eliminating redundant model resets

Influence:

  1. Test switching between all three sort modes (free sort, by category, by name) and verify correct display
  2. Verify section headings update correctly when switching between alphabetary and DDE category modes
  3. Check that scroll position resets to top after each sort switch
  4. Test drag-and-drop reordering still works in free sort mode
  5. Verify context menu (right-click) works correctly in both views
  6. Test rapid back-and-forth switching to check for memory leaks
  7. Verify keyboard navigation (Tab, arrow keys, Enter) works in both views

fix(窗口模式): 优化排序模式切换性能

  1. 在 AppList.qml 中用双视图 visible 切换替代 Loader,避免每次切换 排序模式时销毁和创建组件的开销
  2. AppListView 和 FreeSortListView 现在始终实例化,通过 visible 属性 切换显示,切换时间从 ~120ms 降至 ~2ms
  3. 在 CategorizedSortProxyModel::setCategoryType 中用 setDynamicSortFilter 开关替代 beginResetModel/endResetModel,避免 setSortRole 触发的冗余排序
  4. 新方案使用 layoutAboutToBeChanged/layoutChanged(由 setDynamicSortFilter(true) 触发的 d->sort() 内部发出)替代
    modelReset,使视图移动现有 delegate 而非销毁后重建
  5. 保留隐藏视图 delegate 的额外内存开销约 60-80KB,可忽略不计 (不到进程 RSS 的 0.03%)

Log: 通过用 visible 切换替代 Loader 并消除冗余模型重置,将窗口模式
启动器排序模式切换时间从 ~1000ms 优化至 ~18ms

Influence:

  1. 测试三种排序模式(自由排序、按分类、按名称)之间的切换,验证 显示是否正确
  2. 验证在首字母排序和 DDE 分类模式之间切换时分节标题是否正确更新
  3. 检查每次排序切换后滚动位置是否重置到顶部
  4. 测试自由排序模式下拖拽排序功能是否正常
  5. 验证两个视图的右键上下文菜单是否正常工作
  6. 测试快速来回切换以检查是否存在内存泄漏
  7. 验证两个视图中的键盘导航(Tab、方向键、回车)是否正常

PMS: BUG-367967

Summary by Sourcery

Optimize windowed launcher sort mode switching by keeping both list views instantiated and avoiding full model resets when changing category type.

Enhancements:

  • Always instantiate both categorized and free-sort app list views and switch between them via visibility to eliminate Loader creation/destruction overhead.
  • Adjust keyboard focus and view state handling to route interactions to the currently visible list view.
  • Replace model reset in CategorizedSortProxyModel::setCategoryType with dynamic sort filter toggling so delegate items are preserved during category mode changes.

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Ivy233

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

sourcery-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Reviewer's Guide

Optimizes windowed launcher sort-mode switching by replacing QML Loader-based view swapping with dual always-instantiated views toggled via visibility, and by changing the categorized sort proxy model to use dynamic sort/filter toggling instead of full model resets, preserving delegates and avoiding redundant sorts.

Sequence diagram for CategorizedSortProxyModel::setCategoryType dynamic sort toggle

sequenceDiagram
participant Caller
participant CategorizedSortProxyModel
participant AppListView

Caller->>CategorizedSortProxyModel: setCategoryType(categoryType)
CategorizedSortProxyModel->>CategorizedSortProxyModel: setDynamicSortFilter(false)
Note over CategorizedSortProxyModel: update isFreeSort and config
CategorizedSortProxyModel->>CategorizedSortProxyModel: setDynamicSortFilter(true)
CategorizedSortProxyModel-->>AppListView: layoutAboutToBeChanged
CategorizedSortProxyModel-->>AppListView: layoutChanged
Loading

File-Level Changes

Change Details Files
Switch AppList.qml from Loader-based view instantiation to dual always-present views controlled via visible and a shared container.
  • Introduce isFreeSort property derived from CategorizedSortProxyModel.categoryType and use it to choose the active view.
  • Replace loader-based keyTabTarget, focus handling, and resetViewState routing with conditional delegation to either freeSortView or categoryView.
  • Remove Loader and Component wrappers, instantiate AppListView and FreeSortListView directly inside a shared Item container with anchors.fill, and toggle their visibility based on isFreeSort.
  • Ensure sort-mode switching calls resetViewState on the newly visible view, preserving the behavior of resetting scroll position and state.
qml/windowed/AppList.qml
Optimize CategorizedSortProxyModel::setCategoryType by replacing model resets with dynamic sort/filter toggling to avoid redundant sorts and preserve delegates.
  • Replace beginResetModel/endResetModel with setDynamicSortFilter(false) before, and setDynamicSortFilter(true) after updating categoryType and related configuration.
  • Rely on the internal d->sort() triggered by setDynamicSortFilter(true) to emit layoutAboutToBeChanged/layoutChanged instead of modelReset, so views move existing delegates instead of destroying/recreating them.
  • Remove explicit sort(0) call, depending on the dynamic sort/filter mechanism to perform the required sort once after category type changes.
src/models/categorizedsortproxymodel.cpp
Update SPDX copyright headers to reflect extended years.
  • Extend SPDX-FileCopyrightText year range in AppList.qml from 2024 to 2024 - 2026.
  • Extend SPDX-FileCopyrightText year range in categorizedsortproxymodel.cpp from 2023 to 2023 - 2026.
qml/windowed/AppList.qml
src/models/categorizedsortproxymodel.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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In AppList.qml, isFreeSort is both bound to CategorizedSortProxyModel.categoryType and then imperatively assigned in switchToFreeSort, which breaks the binding and risks state desynchronization; consider either deriving isFreeSort purely from categoryType or storing it only as local state without a binding, but not both.
  • In CategorizedSortProxyModel::setCategoryType, calling setDynamicSortFilter(false); ... setDynamicSortFilter(true); unconditionally may override any pre-existing dynamic sort setting; to avoid changing external behaviour, capture the previous dynamicSortFilter() value and restore it instead of hardcoding true.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In AppList.qml, `isFreeSort` is both bound to `CategorizedSortProxyModel.categoryType` and then imperatively assigned in `switchToFreeSort`, which breaks the binding and risks state desynchronization; consider either deriving `isFreeSort` purely from `categoryType` or storing it only as local state without a binding, but not both.
- In `CategorizedSortProxyModel::setCategoryType`, calling `setDynamicSortFilter(false); ... setDynamicSortFilter(true);` unconditionally may override any pre-existing dynamic sort setting; to avoid changing external behaviour, capture the previous `dynamicSortFilter()` value and restore it instead of hardcoding `true`.

## Individual Comments

### Comment 1
<location path="qml/windowed/AppList.qml" line_range="21-30" />
<code_context>
+    property bool isFreeSort: CategorizedSortProxyModel.categoryType === CategorizedSortProxyModel.FreeCategory
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid breaking the binding on `isFreeSort` by assigning to it directly.

In QML, assigning to this bound property in `switchToFreeSort` will break its binding; after the first assignment it will no longer follow `CategorizedSortProxyModel.categoryType`, risking model/view desync. Either keep it fully derived from `categoryType` and drive switching from that, or make `isFreeSort` an independent `property bool` and update the proxy model explicitly so ownership is clear.
</issue_to_address>

### Comment 2
<location path="src/models/categorizedsortproxymodel.cpp" line_range="22-28" />
<code_context>
-    
-    beginResetModel();
+
+    // Disable dynamic sort to prevent setSortRole from triggering a redundant sort.
+    // We'll do a single sort below via setDynamicSortFilter(true), which uses
+    // layoutAboutToBeChanged/layoutChanged instead of modelReset, preserving delegates.
</code_context>
<issue_to_address>
**issue (bug_risk):** Restore the previous `dynamicSortFilter` state instead of forcing it to `true`.

This change always toggles `dynamicSortFilter` off then on, which means a proxy that was intentionally configured with dynamic sorting disabled will now have it permanently enabled. Instead, cache the existing value (e.g. `const bool oldDynamic = dynamicSortFilter();`), temporarily disable it, and restore `oldDynamic` after updating the category. If `oldDynamic` was false, you may need to call `sort()` once to maintain the new behavior without altering the proxy’s long-term configuration.
</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 thread qml/windowed/AppList.qml
Comment thread src/models/categorizedsortproxymodel.cpp Outdated
1. Replace Loader with dual-view visible switching in AppList.qml to
avoid costly component destruction/creation on each sort mode toggle
2. Both AppListView and FreeSortListView are now always instantiated;
switching is done via the visible property, reducing switch time from
~120ms to ~2ms
3. Replace beginResetModel/endResetModel with setDynamicSortFilter
toggle in CategorizedSortProxyModel::setCategoryType to avoid redundant
sort triggered by setSortRole
4. The new approach uses layoutAboutToBeChanged/layoutChanged (emitted
internally by d->sort() via setDynamicSortFilter(true)) instead of
modelReset, so existing delegates are moved rather than destroyed and
recreated
5. Extra memory cost is ~60-80KB for retaining hidden view delegates,
which is negligible (< 0.03% of process RSS)

Log: Optimized windowed launcher sort mode switch from ~1000ms to ~18ms
by replacing Loader with visible switching and eliminating redundant
model resets

Influence:
1. Test switching between all three sort modes (free sort, by category,
by name) and verify correct display
2. Verify section headings update correctly when switching between
alphabetary and DDE category modes
3. Check that scroll position resets to top after each sort switch
4. Test drag-and-drop reordering still works in free sort mode
5. Verify context menu (right-click) works correctly in both views
6. Test rapid back-and-forth switching to check for memory leaks
7. Verify keyboard navigation (Tab, arrow keys, Enter) works in both
views

fix(窗口模式): 优化排序模式切换性能

1. 在 AppList.qml 中用双视图 visible 切换替代 Loader,避免每次切换
排序模式时销毁和创建组件的开销
2. AppListView 和 FreeSortListView 现在始终实例化,通过 visible 属性
切换显示,切换时间从 ~120ms 降至 ~2ms
3. 在 CategorizedSortProxyModel::setCategoryType 中用
setDynamicSortFilter 开关替代 beginResetModel/endResetModel,避免
setSortRole 触发的冗余排序
4. 新方案使用 layoutAboutToBeChanged/layoutChanged(由
setDynamicSortFilter(true) 触发的 d->sort() 内部发出)替代
modelReset,使视图移动现有 delegate 而非销毁后重建
5. 保留隐藏视图 delegate 的额外内存开销约 60-80KB,可忽略不计
(不到进程 RSS 的 0.03%)

Log: 通过用 visible 切换替代 Loader 并消除冗余模型重置,将窗口模式
启动器排序模式切换时间从 ~1000ms 优化至 ~18ms

Influence:
1. 测试三种排序模式(自由排序、按分类、按名称)之间的切换,验证
显示是否正确
2. 验证在首字母排序和 DDE 分类模式之间切换时分节标题是否正确更新
3. 检查每次排序切换后滚动位置是否重置到顶部
4. 测试自由排序模式下拖拽排序功能是否正常
5. 验证两个视图的右键上下文菜单是否正常工作
6. 测试快速来回切换以检查是否存在内存泄漏
7. 验证两个视图中的键盘导航(Tab、方向键、回车)是否正常

PMS: BUG-367967
@Ivy233 Ivy233 force-pushed the fix/optimize-sort-switch-perf branch from d58ea56 to c78989b Compare July 8, 2026 10:42
@Ivy233

Ivy233 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai

sourcery-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Sorry @Ivy233, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

★ 总体评分:100分

■ 【总体评价】

代码通过移除Loader改用visible属性切换视图并优化模型排序信号,显著提升了UI切换性能且逻辑正确
语法逻辑正确,代码质量良好,性能高效且无安全漏洞,故给予满分

■ 【详细分析】

  • 1.语法逻辑(基本正确)✓

AppList.qml中移除Loader和Component,直接实例化AppListView和FreeSortListView并通过visible控制显示,逻辑正确;CategorizedSortProxyModel::setCategoryType中通过临时禁用再启用dynamicSortFilter触发layoutChanged替代modelReset,逻辑自洽
潜在问题:WindowedFrame.qml移除了sideBar的onSwitchToFreeSort连接,需确认sideBar不再发送该信号或该信号已被废弃,否则可能导致逻辑断裂
建议:确认sideBar的switchToFreeSort信号是否仍有发送方,若无则可安全移除;若有发送方需确保CategorizedSortProxyModel.categoryType能被正确更新

  • 2.代码质量(良好)✓

代码结构清晰,注释详尽地解释了性能优化的原理和内存开销,命名规范符合QML和C++惯例
建议:无需额外改进

  • 3.代码性能(高效)✓

移除Loader的组件销毁/创建过程(约120ms)改用visible切换(约0ms),极大提升了视图切换速度;C++侧避免modelReset保留delegate,减少不必要的对象重建
建议:无需额外改进

  • 4.代码安全(存在0个安全漏洞)✓

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个
代码仅涉及UI视图切换和模型排序逻辑优化,未引入外部输入处理、文件操作或敏感权限操作,无安全风险
建议:无需额外改进

■ 【改进建议代码示例】

// AppList.qml
// 无需修改,代码已达到较优状态

// WindowedFrame.qml
// 建议确认 sideBar.switchToFreeSort 信号是否已废弃
// 如果已废弃,当前移除操作正确;如果未废弃,需确保 categoryType 属性能被正确更新以触发 isFreeSort 变化

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.

2 participants