fix(app-iced): 修 about_center 的平移项(模态从「斜滑」回到「绕中心缩放」)+ 补几何断言
复审 r3 的 P1:about_center 写成 T(-c)·S·T(c),按 iced_core 的「先右后左」算出来是 p → s·p − (1−s)c,不动点在 (−cx,−cy)(窗口外的镜像点)⇒ 卡片开框时从左上斜滑约 (1−s)·c,而不是绕中心缩放。改成 T(c)·S·T(-c)(= s·p + (1−s)c,不动点就是 c),注释同步。 根因是 zoom.rs 里 0 条 #[test](矩阵从没被断言过,967 条全绿抓不住)。新增 tests::about_center_keeps_the_center_fixed:中心不动(<1e-3)、尺寸 = 倍率、缩小时左上角 收向中心;三组矩形 × 三档倍率。把 translate 换回去即红。 顺带补齐「抓得住/抓不住」:components/motion/** 与 motion_sync.rs、platform/motion.rs 里 31 条用例现在全部两侧都有(脚本核对 missing=0)。
This commit is contained in:
1 parent
b6127b4735
commit
add8617494
4 files changed
+86
-6
No files matched your search
@@ -320,8 +320,9 @@ mod tests {
|
|||||||
|
|
||||||
/// **退场窗口内那个框已经关掉、且不可再交互**(tower 的硬要求)。四件事:①逻辑状态当拍就空;
|
/// **退场窗口内那个框已经关掉、且不可再交互**(tower 的硬要求)。四件事:①逻辑状态当拍就空;
|
||||||
/// ②画面还在退场(`dialog_model` 仍 `Some`);③150ms 内再点一次**什么都不发生**(`refreshes`
|
/// ②画面还在退场(`dialog_model` 仍 `Some`);③150ms 内再点一次**什么都不发生**(`refreshes`
|
||||||
/// 不涨 = `on_dialog` 的闸门);④**Esc 那条路径**也挡住(`escape` 的闸门)。**抓不住**:几何级
|
/// 不涨 = `on_dialog` 的闸门);④**Esc 那条路径**也挡住(`escape` 的闸门)。
|
||||||
/// 命中(单测没有 iced 布局树,`zoom.rs` 也不改命中矩形)——那一层由闸门保证。
|
/// **抓得住**:删掉任一道闸门(`on_dialog` / `escape`)或把逻辑关闭改成延后,①②③④ 各自红。
|
||||||
|
/// **抓不住**:几何级命中(单测没有 iced 布局树,`zoom.rs` 也不改命中矩形)——那一层由闸门保证。
|
||||||
#[test]
|
#[test]
|
||||||
fn the_exiting_dialog_is_already_closed_and_swallows_clicks() {
|
fn the_exiting_dialog_is_already_closed_and_swallows_clicks() {
|
||||||
use crate::app::dialog::Message as DialogMessage;
|
use crate::app::dialog::Message as DialogMessage;
|
||||||
|
|||||||
@@ -310,6 +310,10 @@ fn none_scale_is_strictly_zero_not_merely_small() {
|
|||||||
// ------------------------------------------------------------------ 触发判据
|
// ------------------------------------------------------------------ 触发判据
|
||||||
|
|
||||||
/// 重复绘制**永远不会**重新起跑(否则每帧都重启动画 = 永远停不下来)。
|
/// 重复绘制**永远不会**重新起跑(否则每帧都重启动画 = 永远停不下来)。
|
||||||
|
///
|
||||||
|
/// 抓得住:把「标识没变 ⇒ 不起跑」的判断写错(比如每个 view 都 `play_page`)——30 次重复绘制后
|
||||||
|
/// `is_animating()` 仍为真、帧泵永远停不下来,本条红。
|
||||||
|
/// 抓不住:真实屏幕上"重画是否真的只是重画"(那要看 iced 的订阅表,归 `app::subscription` 的用例)。
|
||||||
#[test]
|
#[test]
|
||||||
fn repeated_paints_never_restart_the_animation() {
|
fn repeated_paints_never_restart_the_animation() {
|
||||||
let mut pump = standard();
|
let mut pump = standard();
|
||||||
@@ -324,6 +328,9 @@ fn repeated_paints_never_restart_the_animation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 不归 pages 画的屏(账户 / AI 独立页)**不起跑**;回到某一屏照样跑。
|
/// 不归 pages 画的屏(账户 / AI 独立页)**不起跑**;回到某一屏照样跑。
|
||||||
|
///
|
||||||
|
/// 抓得住:把 `screen == None` 也当成一次换页起跑(会白跑几百毫秒的帧却没人采样)。
|
||||||
|
/// 抓不住:账户页 / AI 页自己的渲染(它们不走 `pages::render`,不归本 pump 管)。
|
||||||
#[test]
|
#[test]
|
||||||
fn screens_rendered_elsewhere_never_start_a_track() {
|
fn screens_rendered_elsewhere_never_start_a_track() {
|
||||||
let mut pump = standard();
|
let mut pump = standard();
|
||||||
|
|||||||
@@ -42,16 +42,22 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 绕**中心**缩放的变换:`T(-c) · S · T(c)`(`Mul` 的乘法序 = 先右后左,见 iced_core 的
|
/// 绕**中心**缩放的变换:`T(c) · S · T(-c)`。
|
||||||
/// `impl Mul for Transformation`)。
|
///
|
||||||
|
/// `Mul` 的乘法序是**先右后左**(`impl Mul for Transformation` 用 `self.0 * rhs.0`,点按
|
||||||
|
/// `p' = M·p` 变换)⇒ 这个式子算出来是 `p → s(p − c) + c = s·p + (1−s)c`,**不动点正是 `c`**。
|
||||||
|
///
|
||||||
|
/// ⚠️ 上一次写反了:`T(-c) · S · T(c)` 会得到 `s·p − (1−s)c`,不动点在 `(−cx, −cy)` ——窗口外的
|
||||||
|
/// 镜像点,于是"缩放"变成了"从左上斜滑进来"(复审 r3 用真 iced_core 实跑抓到:card 中心
|
||||||
|
/// (700,450) 漂到 (644,414))。`tests::about_center_keeps_the_center_fixed` 把这条钉住。
|
||||||
fn about_center(bounds: Rectangle, scale: f32) -> Transformation {
|
fn about_center(bounds: Rectangle, scale: f32) -> Transformation {
|
||||||
let (cx, cy) = (
|
let (cx, cy) = (
|
||||||
bounds.x + bounds.width / 2.0,
|
bounds.x + bounds.width / 2.0,
|
||||||
bounds.y + bounds.height / 2.0,
|
bounds.y + bounds.height / 2.0,
|
||||||
);
|
);
|
||||||
Transformation::translate(-cx, -cy)
|
Transformation::translate(cx, cy)
|
||||||
* Transformation::scale(scale)
|
* Transformation::scale(scale)
|
||||||
* Transformation::translate(cx, cy)
|
* Transformation::translate(-cx, -cy)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Scaled<'a, Message, Theme, Renderer> {
|
struct Scaled<'a, Message, Theme, Renderer> {
|
||||||
@@ -184,3 +190,65 @@ where
|
|||||||
.overlay(tree, layout, renderer, viewport, translation)
|
.overlay(tree, layout, renderer, viewport, translation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use iced::{Point, Size};
|
||||||
|
|
||||||
|
/// **绕中心缩放:中心不动、尺寸按倍率**(复审 r3 的 P1 就是这条从未被断言过)。
|
||||||
|
///
|
||||||
|
/// 抓得住:①把两个 `translate` 乘反(上一版的 bug:不动点落在 `(−cx, −cy)`,窗口外的镜像点
|
||||||
|
/// ⇒ 卡片从左上斜滑进来)——中心断言立刻以几十像素的偏差红;②倍率写错 / 漏乘(尺寸断言红);
|
||||||
|
/// ③把 `about_center` 改成纯缩放(不含平移)——同样红。
|
||||||
|
/// 抓不住:**像素**。这里断言的是几何矩阵;两个渲染后端都按同一个 `Transformation` 画
|
||||||
|
/// (`filled`/`text` 的 `bounds * transformation`),所以"几何等价 ⇒ 像素等价"成立的前提是
|
||||||
|
/// **后端真的把矩阵乘上了**——那一条由真机截图看,不由本用例保证。
|
||||||
|
#[test]
|
||||||
|
fn about_center_keeps_the_center_fixed() {
|
||||||
|
// 默认窗口 1180×760 里居中的卡片(DialogGeometry::max_width = 560)
|
||||||
|
for (rect, scale) in [
|
||||||
|
(
|
||||||
|
Rectangle::new(Point::new(310.0, 220.0), Size::new(560.0, 320.0)),
|
||||||
|
0.96_f32,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Rectangle::new(Point::new(310.0, 220.0), Size::new(560.0, 320.0)),
|
||||||
|
0.5,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Rectangle::new(Point::new(0.0, 0.0), Size::new(384.0, 288.0)),
|
||||||
|
1.04,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let matrix = about_center(rect, scale);
|
||||||
|
let center = rect.center();
|
||||||
|
let moved = center * matrix;
|
||||||
|
assert!(
|
||||||
|
(moved.x - center.x).abs() < 1.0e-3 && (moved.y - center.y).abs() < 1.0e-3,
|
||||||
|
"scale={scale}: 中心必须不动,实测 ({}, {}) 应为 ({}, {})",
|
||||||
|
moved.x,
|
||||||
|
moved.y,
|
||||||
|
center.x,
|
||||||
|
center.y
|
||||||
|
);
|
||||||
|
|
||||||
|
let size = rect.size() * matrix;
|
||||||
|
assert!(
|
||||||
|
(size.width - rect.width * scale).abs() < 1.0e-3
|
||||||
|
&& (size.height - rect.height * scale).abs() < 1.0e-3,
|
||||||
|
"scale={scale}: 尺寸必须是 {scale} 倍,实测 {size:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 缩小时左上角必须往中心方向收(不是往外飞)
|
||||||
|
if scale < 1.0 {
|
||||||
|
let corner = rect.position() * matrix;
|
||||||
|
assert!(
|
||||||
|
corner.x > rect.x && corner.x < center.x,
|
||||||
|
"scale={scale}: 左上角 x 应收向中心,实测 {}",
|
||||||
|
corner.x
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -208,6 +208,10 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// 三态必须**三**个值可区分:`Unknown` 不能被当成 `Reduced`(M256 的硬约束 C)。
|
/// 三态必须**三**个值可区分:`Unknown` 不能被当成 `Reduced`(M256 的硬约束 C)。
|
||||||
|
///
|
||||||
|
/// 抓得住:把 `is_reduced()` 写成「不是 AnimationsOn 就算 Reduced」(= 探针失败就静默关动效,
|
||||||
|
/// 于是某些机器上动效神秘消失)——第一条断言立刻红。
|
||||||
|
/// 抓不住:真机上这三个值读得对不对(那要改系统设置,见报告的未验清单)。
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_is_not_reduced() {
|
fn unknown_is_not_reduced() {
|
||||||
assert!(!ReduceMotion::Unknown.is_reduced());
|
assert!(!ReduceMotion::Unknown.is_reduced());
|
||||||
|
|||||||
Reference in new issue
Block a user