Skip to content

fix(desktop): correct context menu position under uiScale - #213

Merged
zerx-lab merged 1 commit into
mainfrom
farm/cc75e1f9/fix-context-menu-uiscale-position
Jul 29, 2026
Merged

fix(desktop): correct context menu position under uiScale#213
zerx-lab merged 1 commit into
mainfrom
farm/cc75e1f9/fix-context-menu-uiscale-position

Conversation

@ZerxLabBot

Copy link
Copy Markdown
Collaborator

Repro

用户在 Windows 客户端 v0.2.3 将「设置 → 外观 → 界面缩放」(uiScalelib/src/theme/theme_provider.dart,允许 0.8–1.5)调至非 1.0 后,右键任意任务行弹出的操作菜单(showTaskContextMenulib/src/widgets/task_list_item.dart)出现在远离鼠标点击处的位置,越靠窗口右下角点击偏移越大(见截图)。

机器人环境无法构建/运行 Flutter,本 PR 走静态复现:沿 GestureDetector.onTapDownshowTaskContextMenushowContextMenulib/src/widgets/context_menu.dart)的调用链读代码,结合 lib/main.dart:1011-1019uiScale 接线,推导出的偏移量公式(点击点坐标 × (scale-1))与截图中箭头指示的偏移在数量级和方向上吻合(uiScale≈1.2 时预测偏移 ≈(198,46)px,截图观测约 (190,50)px)。

Cause

lib/main.dart:1011-1019uiScale != 1.0 时,WidgetsApp.builderUiScaleWidgetlib/src/widgets/ui_scale_widget.dart)包裹整个 Navigator/Overlay 子树——子树在 constraints.biggest / scale 的缩小逻辑尺寸下布局,paint 时再整体放大 scale 倍;同时用 MediaQuery.copyWith(size: mq.size / scale)MediaQuery.size 同步缩小,以匹配 Overlay 缩放子树内的本地坐标系。

lib/src/widgets/context_menu.dartshowContextMenu 却把 TapDownDetails.globalPosition(原始指针事件的设备全局坐标,不受 RenderUiScale 变换影响)直接当作插入 OverlayPositioned(left, top) 使用——但 Positioned 是相对 Overlay 缩小后的本地坐标系布局的,两者单位不一致,产生随点击点位置成比例放大的偏移。showContextMenu 是全应用唯一的右键菜单入口(任务行/任务组/侧栏/队列/表头列设置等均复用它),因此该缺陷影响所有右键菜单。

Fix

  • lib/src/widgets/context_menu.dart:在计算 left/top 前,经 Overlay.of(context).context.findRenderObject() 取 Overlay 自身 RenderBox,用 globalToLocal 把设备全局坐标转换为 Overlay 本地坐标,再据此定位菜单(uiScale == 1.0 时祖先链无变换,globalToLocal 是恒等映射,不改变现有行为)。

Verification

未在机器人环境中构建或测试 —— 运行环境无法承载本项目的 Flutter 工具链。本改动经静态审查:

  • 通读 lib/main.dartuiScale 接线(UiScaleWidget + MediaQuery.copyWith)与 lib/src/widgets/ui_scale_widget.dartRenderUiScaleperformLayout/paint/applyPaintTransform),确认 applyPaintTransform 正确暴露了缩放矩阵,globalToLocal 沿祖先链求逆能正确还原该变换。
  • 修复模式与 Flutter SDK 自身 showMenu() 的标准写法一致(用 Overlay.of(context).context.findRenderObject() as RenderBox 做坐标系转换基准),未引入新依赖或新错误处理路径。
  • 确认 showContextMenu 是仓库内唯一的右键菜单构造函数(task_list_item.dart/task_group_card.dart/sidebar.dart/header_bar.dart/task_list.dart/manifest_select_view.dart/new_download_dialog.dart/quick_download_form.dart 均复用它),单点修复覆盖全部调用方,未发现遗漏。
  • uiScale == 1.0(默认值)路径未改变行为:无 UiScaleWidget 包裹时祖先链无变换,globalToLocal 退化为恒等映射。

请维护者执行验证:

  • flutter analyze
  • 手动验证:设置界面缩放为 1.2/0.8,右键任务行/任务组/侧栏分类,确认菜单出现在鼠标点击处而非偏移

已知未覆盖:未新增自动化测试(该坐标变换依赖真实渲染管线的 RenderBox.globalToLocal,机器人环境无法运行 widget test 验证断言正确性;建议维护者补充一个挂载 MaterialApp + 非 1.0 uiScale 场景、模拟点击、断言 Overlay 菜单 Positioned.left/top 的 widget test)。

Fixes #193

showContextMenu positioned the Overlay entry using the raw
GestureDetector globalPosition (real device coordinates from the
pointer event), while main.dart wraps the app's Navigator/Overlay in
UiScaleWidget whenever uiScale != 1.0, laying out that subtree (and
Overlay's own local coordinate space) at screen size / scale and
painting it back up by scale.

Positioned inside that Overlay is relative to the scaled-down local
frame, so feeding it the unscaled global tap position produced an
offset proportional to (scale - 1) times the distance from the
window origin — the further from the top-left the click, the further
the menu drifted from the cursor.

Convert globalPosition through the Overlay's own RenderBox via
globalToLocal before using it as left/top. This is a no-op when
uiScale == 1.0 (no transform in the ancestor chain), and correctly
inverts the RenderUiScale paint transform otherwise. showContextMenu
is the single entry point for every right-click menu in the app
(task rows, groups, sidebar, queues, header bar), so the fix covers
all of them.

Fixes #193
@zerx-lab
zerx-lab merged commit 52a9e8d into main Jul 29, 2026
1 check 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.

🐛 [Bug] 菜单距离鼠标太远显示

2 participants