fix(tray): refresh DPI coordinates after cross-monitor show (#8595)

This commit is contained in:
funkpopo authored and GitHub committed 2026-10-20 16:39:24 +08:00
1 parent 91ab270e2a
commit 74cf8e6f0b
2 files changed
+72 -2

No files matched your search

+10 -2
View File
@@ -485,9 +485,17 @@ export function TrayPanel() {
useEffect(() => {
const update = () => setPixelRatio(window.devicePixelRatio || 1);
// 跨屏时 DPR 可能晚于显示事件更新,固定画布也不一定触发 resize。
// 每次 DPR 变化后重新订阅,才能继续检测下一次跨屏或 WebView 缩放。
const media = window.matchMedia?.(`(resolution: ${pixelRatio}dppx)`);
media?.addEventListener("change", update);
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
update();
return () => {
media?.removeEventListener("change", update);
window.removeEventListener("resize", update);
};
}, [pixelRatio]);
const load = useCallback(async () => {
try {
+62
View File
@@ -153,6 +153,7 @@ describe("TrayPanel window layout", () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
window.localStorage.removeItem("cc-switch-tray-tab");
delete document.documentElement.dataset.glass;
delete document.documentElement.dataset.layout;
@@ -390,6 +391,67 @@ describe("TrayPanel window layout", () => {
},
);
it.each([
[1, 1.5, 2],
[2, 1.5, 1],
])(
"updates clipping without resize when DPR arrives after show (%s → %s → %s)",
async (initialScale, secondScale, thirdScale) => {
const ratio = vi.spyOn(window, "devicePixelRatio", "get");
ratio.mockReturnValue(initialScale);
const mediaQueries: Array<
EventTarget & { media: string; matches: boolean }
> = [];
vi.stubGlobal("matchMedia", (query: string) => {
const media = Object.assign(new EventTarget(), {
media: query,
matches: query === `(resolution: ${window.devicePixelRatio}dppx)`,
});
mediaQueries.push(media);
return media as MediaQueryList;
});
const { container } = await openPanel({
...windows,
scaleFactor: initialScale,
});
const main = container.querySelector<HTMLElement>(".tp-card")!;
for (const [index, scaleFactor] of [secondScale, thirdScale].entries()) {
const seq = index + 2;
// 原生窗口收到布局后才跨屏:显示事件仍读到旧屏的 DPR。
await act(async () => {
emitTauriEvent(SHOW_EVENT, { ...windows, seq, scaleFactor });
});
await paint();
const calls = vi.mocked(trayPanelApi.layout).mock.calls.length;
// 画布尺寸不变,没有 resize;只分发匹配状态确实改变的媒体查询事件。
await act(async () => {
ratio.mockReturnValue(scaleFactor);
for (const media of [...mediaQueries]) {
const matches = media.media === `(resolution: ${scaleFactor}dppx)`;
if (media.matches !== matches) {
media.matches = matches;
media.dispatchEvent(new Event("change"));
}
}
});
await paint();
expect(main).toHaveStyle({ width: "320px", maxHeight: "620px" });
expect(trayPanelApi.layout).toHaveBeenCalledTimes(calls + 1);
expect(trayPanelApi.layout).toHaveBeenLastCalledWith(
expect.objectContaining({
seq,
width: 620,
height: 760,
cards: [{ x: 300, y: 440, width: 320, height: 320 }],
}),
);
}
},
);
it("limits both cards to the available height on small scaled displays", async () => {
const { container } = await openPanel({ ...windows, boundsTop: -180 });
fireEvent.click(