fix(motion): M305 r1 —— P1 空轨道重复脉冲 + 三条 P2(进场被删跳零 / 纯重排不落终态 / 遮罩取色与口径)
- P1-1:observe_progress 在「没有轨道且目标没变」时直接 return false。少了这一条, ratio 恒为 1.0 之后每条 stage 事件都会插一条 from==to 的空轨道 ⇒ 200ms 后再闪一次 150ms 脉冲(违反「一次收束不循环」+ 空烧帧)。用例 a_repeated_reading_after_the_pulse… - P2-1:visibility_of 改成按**当前采样**取(原来取 track.from ⇒ 进场途中被删的行 0→0, 高度当拍归零、下面整列上跳)。用例 deleting_a_row_mid_enter_does_not_jump_to_zero - P2-2:遮罩底色 Card → Canvas(版本行脚下就是画布;深色档会露出亮一档) - P2-3:纯重排(同 id 换序)当场把 order 对齐 current。没有轨道 ⇒ advance 早退 ⇒ expire 不跑 ⇒ order 会永远停在旧序。用例 a_pure_reorder_lands_on_the_new_order_at_once - P2-4:fade.rs 收成唯一口径(预合成 = 恒不透明;子树淡出是唯一例外,且必须自成一层), reveal.rs 指向它并登记 interaction α 守卫扫不到这一处的盲区 - 出图补深色档 list-exit-dark.png,probe 复跑 PASS(墨色 110→148 / 105→154)
This commit is contained in:
1 parent
ae33674293
commit
d24ef21bee
8 files changed
+179
-18
No files matched your search
@@ -2,23 +2,35 @@
|
||||
//!
|
||||
//! ## 这条纪律是什么(D27)
|
||||
//!
|
||||
//! 「CPU 档零半透明」:整窗重绘里每多一层半透明,软件渲染就多一遍混合。仓内唯一的 α 出口是
|
||||
//! 「CPU 档零半透明」:整窗重绘里每多一层半透明,软件渲染就多一遍混合。仓内 α 的正统出口是
|
||||
//! **令牌层预合成**(`c.with_alpha(α).over(底)`,见 `interaction::disabled` 与
|
||||
//! `dialog/view.rs::backdrop_color_at`):产物**恒不透明**。
|
||||
//! `interaction/mod.rs` 的用例把这条钉成硬线(「为了淡出改用 `Color { a: 0.5 }`」直接红)。
|
||||
//!
|
||||
//! 本模块只剩一件工具:[`mix`]——两个**不透明**色之间按进度插值,用于「进度条到达 100% 时那一下
|
||||
//! 本模块的工具是 [`mix`]——两个**不透明**色之间按进度插值,用于「进度条到达 100% 时那一下
|
||||
//! 强调收束」(`feedback/progress.rs::linear_with`)。它不改几何、不改厚度,所以**不引起重排**。
|
||||
//!
|
||||
//! ## 为什么这里没有「整行淡出」
|
||||
//! ## 唯一的例外:子树淡出(口径写在这里,别处不许再有第二种说法)
|
||||
//!
|
||||
//! 列表行的淡出走的是 `motion/reveal.rs` 的收尾遮罩(原因写在那里:`Element<'a>` 借 `&'a Ui`,
|
||||
//! 视图函数里现造的「淡化版 Ui」撑不到返回;把它泄漏成 `'static` 会让每帧每行泄漏一份 49 色方案)。
|
||||
//! 行内容因此**永远逐像素实色绘制**,只有裁切框与那一层遮罩在变。
|
||||
//! | 场景 | 做法 | 产物透明度 |
|
||||
//! |---|---|---|
|
||||
//! | 颜色插值(进度条收束、遮罩浓度、禁用层) | [`mix`] / `with_alpha(α).over(底)` | **恒 1.0** |
|
||||
//! | **一整棵子树淡出**(列表行) | `motion/reveal.rs` 的收尾遮罩:与**画布同色**的 quad,α = 1 − 可见度,且**必须自成一层** | 动画期间 < 1,**静息帧严格不画** |
|
||||
//!
|
||||
//! 为什么子树淡出做不到「预合成」:预合成要求先知道被盖住的像素(`content.with_alpha(α).over(底)`),
|
||||
//! 而「一行的内容」在 `Element` 里是黑盒;唯一可行的替代是把整份 `Scheme` 预合成后换一个 `Ui`
|
||||
//! 传下去,但那要求淡化的 `Ui` 活得比 `Element<'a>` 久——视图函数里现造的 `Ui` 撑不到返回,
|
||||
//! 泄漏成 `'static` 则是每帧每行泄漏一份 49 色方案(长会话无界增长)。
|
||||
//!
|
||||
//! ⚠️ **守卫盲区(r1 复审 P2-4 要求写清)**:`interaction/mod.rs` 那条「中间帧也必须不透明」的
|
||||
//! 用例只扫**状态层取色**,**扫不到** `motion/reveal.rs::draw` 递出去的遮罩(那一处是渲染器的
|
||||
//! `fill_quad`,不经过任何 `Style`)。所以「遮罩的 α 只在动画期间出现、静息帧一个 quad 都不画」
|
||||
//! 这条口径改由 `pages/download/rows/motion_tests.rs` 的结构断言 + 出图基线盯着
|
||||
//! (静息帧与改造前逐像素相同,见 `DevDocs/artifacts/m305/`)。
|
||||
|
||||
use unlucky_design::color::Color;
|
||||
|
||||
/// 两个**不透明**颜色之间按 `t` 的插值(预合成口径,同 [`faded`] 的纪律)。
|
||||
/// 两个**不透明**颜色之间按 `t` 的插值。
|
||||
///
|
||||
/// 用在「进度条收束」这类没有背景色可叠的地方:`to.with_alpha(t).over(from)` 得到的是
|
||||
/// 「把 `to` 按 t 盖在 `from` 上」的**实色**,产物恒不透明(D27);端点 `t = 0` / `1`
|
||||
|
||||
@@ -162,6 +162,14 @@ impl Pump {
|
||||
self.progress.shown = target;
|
||||
return false;
|
||||
}
|
||||
// **没有轨道但目标没变** = 这条事件什么也没带来:不许为了「再更新一次」插一条
|
||||
// `from == to` 的空轨道。少了这一行,脉冲走完之后**每一条 stage 事件都会再闪一次**
|
||||
// (`ratio()` 在最后一个文件下完就恒为 1.0,而 Installing/Registering 阶段每条事件都
|
||||
// 会走一遍 `observe`),既违反「一次收束不循环」,也空烧 200ms + 150ms 的帧
|
||||
// ——这正是 M305 r1 复审的 P1-1。
|
||||
if self.progress.track.is_none() && (target - self.progress.shown).abs() < f32::EPSILON {
|
||||
return false;
|
||||
}
|
||||
if self
|
||||
.progress
|
||||
.track
|
||||
|
||||
@@ -137,12 +137,16 @@ impl Rows {
|
||||
.any(|(current, id)| current != id)
|
||||
}
|
||||
|
||||
/// 现在的可见度(没有轨道 = 1.0)。
|
||||
fn visibility_of(&self, id: &str) -> f32 {
|
||||
/// **这一帧**的可见度(没有轨道 = 1.0)。
|
||||
///
|
||||
/// 必须取**当前采样**而不是 `track.from`:进场走到一半就被删的行,`from` 是 0(它从不可见
|
||||
/// 开始),拿它当退场起点会得到「0 → 0」——可见度与高度当拍归零、下面整列上跳,正是任务①
|
||||
/// 禁的闪跳。退场中途回来的那一侧本来就走采样,两个方向必须同一条口径(M305 r1 P2-1)。
|
||||
fn visibility_of(&self, id: &str, now_ms: f64) -> f32 {
|
||||
self.tracks
|
||||
.iter()
|
||||
.find(|(tracked, _)| tracked == id)
|
||||
.map_or(1.0, |(_, track)| track.from)
|
||||
.map_or(1.0, |(_, track)| track.sample(now_ms - track.start_ms))
|
||||
}
|
||||
|
||||
/// 换一条轨道(同 id 覆盖:中途反向就接着当前进度走)。
|
||||
@@ -270,6 +274,7 @@ impl Pump {
|
||||
};
|
||||
let enter_ms = f64::from(scale.ms(spec.enter.duration));
|
||||
let exit_ms = f64::from(scale.ms(spec.exit.duration));
|
||||
let now = self.elapsed_ms();
|
||||
|
||||
// ① 离开的:用**旧顺序**里的位置算错位(用户看到的原本次序)。
|
||||
let leaving: Vec<(String, usize)> = self
|
||||
@@ -280,8 +285,9 @@ impl Pump {
|
||||
.filter(|(_, id)| !ids.contains(&id.as_str()))
|
||||
.map(|(index, id)| (id.clone(), index))
|
||||
.collect();
|
||||
let left = leaving.len();
|
||||
for (id, index) in leaving {
|
||||
let from = self.rows.visibility_of(&id);
|
||||
let from = self.rows.visibility_of(&id, now);
|
||||
self.rows
|
||||
.retarget(&id, from, 0.0, spec.exit, exit_ms, stagger, index);
|
||||
}
|
||||
@@ -300,7 +306,7 @@ impl Pump {
|
||||
(!known || retracting).then(|| ((*id).to_owned(), index))
|
||||
})
|
||||
.collect();
|
||||
let now = self.elapsed_ms();
|
||||
let arrived = arriving.len();
|
||||
for (id, index) in arriving {
|
||||
// 退场中途回来(还有轨道):接着**当前**可见度走,不跳回 0;全新的行从 0 起。
|
||||
let from = match self.rows.tracks.iter().find(|(tracked, _)| tracked == &id) {
|
||||
@@ -312,7 +318,14 @@ impl Pump {
|
||||
.retarget(&id, from, 1.0, spec.enter, enter_ms, stagger, index);
|
||||
}
|
||||
|
||||
// ③ **纯重排**(同一批 id 换了顺序):没有进、也没有出,所以一条轨道都不建——
|
||||
// 而 `advance` 在没轨道时早退、`expire` 也就不跑,`order` 会永远停在旧序
|
||||
// (`Rows::expire` 是它唯一的归位处)。所以这里当场把画的顺序对齐真值
|
||||
// (M305 r1 P2-3;今天下载页没有排序切换,属机制级隐患)。
|
||||
self.rows.current = ids.iter().map(|id| (*id).to_owned()).collect();
|
||||
if left == 0 && arrived == 0 {
|
||||
self.rows.order.clone_from(&self.rows.current);
|
||||
}
|
||||
self.rows.is_animating()
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,20 @@
|
||||
//! | 子内容的**排版** | `content_height`(整段过渡里不变 ⇒ 不重排) |
|
||||
//! | 自己占的**格子**(= 裁切框) | `visible_height`(逐帧变,由行的可见度给) |
|
||||
//!
|
||||
//! ## 绘制
|
||||
//! ## 绘制:裁切 + **一层与画布同色的收尾遮罩**
|
||||
//!
|
||||
//! 走 `Renderer::with_layer(bounds)`(tiny-skia 是 `push_clip`,wgpu 是同族的层栈),超出的部分
|
||||
//! **画不出来**——「收起到 0」在像素上就是「一格都不占」,不需要半透明(D27 的单一口径)。
|
||||
//! 淡出是另一件事,在 `motion/fade.rs`(色板预合成)。
|
||||
//! 裁切走 `Renderer::with_layer(bounds)`(tiny-skia 是 `push_clip`,wgpu 是同族的层栈):超出的
|
||||
//! 部分画不出来——「收起到 0」在像素上就是「一格都不占」。
|
||||
//!
|
||||
//! 淡出走 [`revealed`] 的 `veil` 参数:一层**与脚下画布同色**(`MaterialLayer::Canvas`)的 quad,
|
||||
//! `α = 1 − 可见度`,**且必须自成一层**。为什么是 α 而不是预合成、为什么必须自成一层:
|
||||
//!
|
||||
//! | 问题 | 答案 |
|
||||
//! |---|---|
|
||||
//! | 为什么不用预合成(`with_alpha(α).over(底)`,D27 的正统口径) | 预合成要先知道被盖住的像素,而「一行的内容」在 `Element` 里是黑盒;替代方案(把整份 `Scheme` 预合成后传一个 `Ui` 下去)要求那个 `Ui` 活得比 `Element<'a>` 久——视图函数里造不出来,泄漏成 `'static` 则是每帧每行泄漏一份 49 色方案 |
|
||||
//! | 为什么必须 `with_layer` | tiny-skia 的绘制按「四边形 → 三角 → 图 → 文字」分批(`iced_tiny_skia/src/lib.rs::present`),同层里文字永远排在四边形之后。第一版把遮罩塞进当前层:红色 α=0.3 的遮罩实测**一个文字像素都没盖住**(整图 0 个偏红像素);改成子层后同一实验 422 个偏红像素、正好落在动的那两行(y 262..307) |
|
||||
//! | 静息帧 | `veil = None` ⇒ **一个 quad 都不画**,与改造前逐像素相同(出图基线与 M305 之前同源) |
|
||||
//! | 口径归属 | 这是全仓**唯一**一处「把 α 交给渲染器」的地方,规则的真源写在 `motion/fade.rs` 的模块文档。`interaction/mod.rs` 的 α 守卫只扫状态层取色、**扫不到**这里(不经过任何 `Style`)——盲区已登记在 `fade.rs` |
|
||||
//!
|
||||
//! ## 与 `clip.rs` 的区别(为什么不再写一个通用件)
|
||||
//!
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//! | `pump/progress.rs` 的 `Progress::value` 里把插值那一行换成 `Some(track.to)`(**改回硬跳**) | `the_value_chases_the_target_instead_of_jumping` 的「没有一帧落在两端之间」(实测见报告) |
|
||||
//! | `pump/progress.rs` 的 `expire` 里删掉「`track.to >= 1.0` 才排收束」 | `the_settle_fires_once_and_is_zero_at_both_ends`(中途跳变也会亮一下) |
|
||||
//! | `pump/progress.rs` 的 `settle_amount` 去掉 `if t >= 1.0 { return 0.0 }` | 同一条用例的「末帧必须严格为 0」(脉冲会停在中途) |
|
||||
//! | `pump/progress.rs` 的 `observe_progress` 里删掉「没有轨道但目标没变 ⇒ return false」那一段 | `a_repeated_reading_after_the_pulse_does_not_fire_it_again`(r1 P1-1) |
|
||||
//! | `pump/progress.rs` 的 `observe_progress` 里删掉 `is_off()` 那一支 | `no_scale_renders_the_target_itself`(`samples()` 非 0) |
|
||||
//! | `pump/progress.rs` 的 `progress_value` 里删掉「目标不符就给目标」 | `a_model_that_skips_the_observe_is_not_stuck_midway` |
|
||||
|
||||
@@ -122,6 +123,41 @@ fn the_settle_fires_once_and_is_zero_at_both_ends() {
|
||||
let _ = spec;
|
||||
}
|
||||
|
||||
/// **同一条读数再报一次不许再排一次收束**(r1 P1-1,产品可达)。
|
||||
///
|
||||
/// 场景:最后一个文件下完之后 `ratio()` 恒为 1.0,而 `Installing` / `Registering` 阶段每条 stage
|
||||
/// 事件都会再走一遍 `observe`(`host/install.rs:216,239` → refresh_view → `from_snapshot`)。
|
||||
/// 少了那条短路,每来一条事件就插一条 `from == to` 的空轨道 ⇒ 200ms 后**再闪一次** 150ms 脉冲,
|
||||
/// 既违反「一次收束不循环」,也空烧 350ms 的帧。
|
||||
///
|
||||
/// 抓得住:删掉 `observe_progress` 里那条 `track.is_none() && 目标没变` 的短路。
|
||||
/// 抓不住:脉冲本身的观感(出图负责)。
|
||||
#[test]
|
||||
fn a_repeated_reading_after_the_pulse_does_not_fire_it_again() {
|
||||
let mut pump = standard();
|
||||
pump.observe_progress("install-1", 0.98);
|
||||
settle(&mut pump);
|
||||
assert!(pump.observe_progress("install-1", 1.0));
|
||||
// 推完追值 + 收束(一次)
|
||||
settle(&mut pump);
|
||||
assert!(!pump.is_animating(), "前提:脉冲已经走完");
|
||||
|
||||
for beat in 0..5 {
|
||||
assert!(
|
||||
!pump.observe_progress("install-1", 1.0),
|
||||
"第 {beat} 条事件不该起轨道(同一条读数)"
|
||||
);
|
||||
assert!(!pump.is_animating(), "第 {beat} 条事件不该让泵再转起来");
|
||||
assert_eq!(
|
||||
pump.progress_settle("install-1", 1.0),
|
||||
0.0,
|
||||
"第 {beat} 条事件不该再闪一次收束"
|
||||
);
|
||||
}
|
||||
// 真变了才动(0.5 是回落,重装/重试)
|
||||
assert!(pump.observe_progress("install-1", 0.5));
|
||||
}
|
||||
|
||||
/// **`MotionScale::NONE` 严格等于目标**、零采样(任务里那条「值仍是最终值」的断言版)。
|
||||
#[test]
|
||||
fn no_scale_renders_the_target_itself() {
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
//! | `pump/rows.rs` 的 `frame` 里把 `track.sample(..)` 换成 `track.to`(**改回硬跳**) | `an_enter_row_climbs_and_an_exit_row_descends_on_the_same_clock` 的中间帧断言(可见度要么 0 要么 1) |
|
||||
//! | `pump/rows.rs` 的 `observe_rows` 里删掉 `scale.is_off()` 那一支 | `no_scale_lands_every_row_on_the_final_state`(`samples()` 非 0、`needs_frames()` 为真) |
|
||||
//! | `pump/rows.rs` 的 `retarget` 里 `slots` 用 `index` 而不是 `min(index, max_stagger)` | `the_stagger_is_capped_so_a_long_list_does_not_wait_forever` |
|
||||
//! | `pump/rows.rs` 的 `visibility_of` 里把「按当前采样」换回 `track.from` | `deleting_a_row_mid_enter_does_not_jump_to_zero`(r1 P2-1) |
|
||||
//! | `pump/rows.rs` 的 `observe_rows` 里删掉「纯重排当场归位」那一段 | `a_pure_reorder_lands_on_the_new_order_at_once`(r1 P2-3) |
|
||||
|
||||
use super::super::pump::Pump;
|
||||
use super::super::pump::RowFrame;
|
||||
@@ -233,6 +235,57 @@ fn observing_the_same_list_twice_is_a_no_op() {
|
||||
assert_eq!(visibility(&pump, "c"), mid, "进度不许被重置");
|
||||
}
|
||||
|
||||
/// **进场走到一半就被删的行不许当场消失**(r1 P2-1)。
|
||||
///
|
||||
/// 场景:搜索框连打 / 两次分类点选间隔 < 200ms —— 上一次的进场还没走完,这一行的 id 就没了。
|
||||
/// 拿 `track.from`(进场时是 0)当退场起点会得到「0 → 0」:可见度与高度当拍归零、下面整列上跳,
|
||||
/// 正是任务①禁的闪跳。反方向(退场中途回来)本来就接当前采样,两个方向必须同一条口径。
|
||||
///
|
||||
/// 抓得住:`visibility_of` 用 `track.from` 而不是当前采样。
|
||||
/// 抓不住:错位那几行的相位(另有 `the_stagger_is_capped_…` 盯着)。
|
||||
#[test]
|
||||
fn deleting_a_row_mid_enter_does_not_jump_to_zero() {
|
||||
let mut pump = standard();
|
||||
// b 在**第 0 位**:错位档 0,进场立刻开始。
|
||||
pump.observe_rows(&["a"]);
|
||||
assert!(pump.observe_rows(&["b", "a"]));
|
||||
for _ in 0..3 {
|
||||
pump.advance(FRAME_MS);
|
||||
}
|
||||
let mid = visibility(&pump, "b");
|
||||
assert!(mid > 0.0 && mid < 1.0, "前提:进场走到一半({mid})");
|
||||
let height_before = pump.row_frame("b").visibility;
|
||||
|
||||
// 这一行在进场中途被删掉
|
||||
assert!(pump.observe_rows(&["a"]));
|
||||
let after = visibility(&pump, "b");
|
||||
assert_eq!(
|
||||
after, height_before,
|
||||
"退场起点必须是**当前**可见度({height_before}),不许当场归零"
|
||||
);
|
||||
assert!(after > 0.0, "这一行还在画、还必须看得见:{after}");
|
||||
// 之后才慢慢退
|
||||
pump.advance(FRAME_MS);
|
||||
assert!(visibility(&pump, "b") < after, "接着往下退");
|
||||
}
|
||||
|
||||
/// **纯重排当场落到新序**(r1 P2-3):同一批 id 换顺序时不建轨道,但画的顺序必须立刻对齐。
|
||||
///
|
||||
/// 抓得住:`observe_rows` 少了「没有进/出就把 `order` 对齐 `current`」那一段 —— 那时没有轨道
|
||||
/// ⇒ `advance` 早退 ⇒ `expire` 不跑 ⇒ `order` 永远停在旧序(`Rows::expire` 是它唯一的归位处)。
|
||||
#[test]
|
||||
fn a_pure_reorder_lands_on_the_new_order_at_once() {
|
||||
let mut pump = standard();
|
||||
pump.observe_rows(&["a", "b", "c"]);
|
||||
pump.observe_rows(&["c", "b", "a"]);
|
||||
assert!(!pump.is_animating(), "纯重排不建轨道");
|
||||
assert_eq!(
|
||||
pump.rows_drawn(),
|
||||
vec!["c", "b", "a"],
|
||||
"没有轨道就没有 expire,顺序必须当场归位"
|
||||
);
|
||||
}
|
||||
|
||||
/// 退场中途又回列表:接着当前进度往回走,**不许跳回 0**(错位入也不许闪一下)。
|
||||
#[test]
|
||||
fn a_row_that_returns_mid_exit_does_not_jump() {
|
||||
|
||||
@@ -122,10 +122,13 @@ pub(super) fn rows_column<'a>(
|
||||
// 于是末帧与静息逐像素相同。为什么不是「把 α 交给行内容」、为什么遮罩必须自成一层,
|
||||
// 见 `motion/reveal.rs` 的模块文档(两件事都有实测记录)。
|
||||
let veil = (visibility < 1.0).then(|| {
|
||||
let card = ui.layer(MaterialLayer::Card);
|
||||
// **底色是 Canvas,不是 Card**(M305 r1 P2-2):版本行坐在页面的画布上
|
||||
// (行静息 `fill = None`,脚下那层是 `frame::page` 的 Canvas)。取成 Card 会让
|
||||
// 遮罩比底色亮一档:浅色 249 vs 247(几乎看不出),深色 tone 6 vs 10(露馅)。
|
||||
let under = ui.layer(MaterialLayer::Canvas);
|
||||
iced::Color {
|
||||
a: (1.0 - visibility).clamp(0.0, 1.0),
|
||||
..card
|
||||
..under
|
||||
}
|
||||
});
|
||||
entries = entries.push(
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
//! | `rows/list.rs` 把 `keyed::Column` 换回 `Column`(按**下标**比对) | `inserting_a_row_keeps_every_unchanged_rows_node`(a/b/c 的树节点会全换一遍) |
|
||||
//! | `rows/list.rs` 的 `row_key` 里改成 `0`(key 恒等) | 同一条用例(新行会顶掉 a 的节点) |
|
||||
//! | `pump/rows.rs` 的 `observe_rows` 删掉 `is_off()` 分支 | `no_scale_lands_on_the_final_state_without_frames`(`needs_frames()` 会为真) |
|
||||
//! | `rows/list.rs` 的遮罩底色从 `MaterialLayer::Canvas` 改回 `Card` | `the_veil_is_canvas_coloured_and_only_exists_while_moving`(r1 P2-2) |
|
||||
//! | `rows/list.rs` 里把 `(visibility < 1.0).then(…)` 改成无条件 `Some(…)` | 同一条用例的「静息帧不画」(r1 P2-4) |
|
||||
//! | `rows/list.rs` 里不再按 `motion::rows::drawn()` 取行(改回遍历 `visible`) | `the_list_is_fed_by_the_pump_order` |
|
||||
|
||||
use std::any::Any;
|
||||
@@ -196,6 +198,31 @@ fn the_list_is_fed_by_the_pump_order() {
|
||||
}
|
||||
}
|
||||
|
||||
/// 遮罩的两条口径(r1 P2-2 / P2-4):**底色取 Canvas**、**静息帧不递**。
|
||||
///
|
||||
/// 为什么底色必须是 Canvas:版本行坐在页面的画布上(行静息 `fill = None`,脚下是 `frame::page`
|
||||
/// 的 Canvas)。取成 Card(`SurfaceContainerLow`)会让遮罩比底色亮一档——浅色 249 vs 247 几乎
|
||||
/// 看不出,深色 tone 6 vs 10 会露馅。
|
||||
///
|
||||
/// 为什么静息帧不许递:`veil = Some(..)` 的 α 是「交给渲染器的半透明」,是全仓唯一一处
|
||||
/// (口径见 `motion/fade.rs`);`visibility >= 1` 时必须一个 quad 都不画,否则静息像素与改造前不同。
|
||||
#[test]
|
||||
fn the_veil_is_canvas_coloured_and_only_exists_while_moving() {
|
||||
let source = include_str!("list.rs");
|
||||
assert!(
|
||||
source.contains("MaterialLayer::Canvas"),
|
||||
"遮罩底色必须是画布层(行脚下那一层)"
|
||||
);
|
||||
assert!(
|
||||
!source.contains("MaterialLayer::Card"),
|
||||
"遮罩底色取成 Card 会比底色亮一档(深色档露馅)"
|
||||
);
|
||||
assert!(
|
||||
source.contains("(visibility < 1.0).then("),
|
||||
"静息帧(visibility = 1)不许递遮罩:那是一个交给渲染器的半透明 quad"
|
||||
);
|
||||
}
|
||||
|
||||
/// `MotionScale::NONE`:列表变化**当场就是最终态**,而且一帧都不要。
|
||||
#[test]
|
||||
fn no_scale_lands_on_the_final_state_without_frames() {
|
||||
|
||||
Reference in new issue
Block a user