feat(settings): M299 选中项收口到 selected_fill + 分组导航改真按钮 + tabs 用 status
- pages/settings/frame.rs:分组导航从 mouse_area 换成 button(四态可辨), 几何不变(ShapeScale::Full 胶囊),颜色走唯一的 selected_fill;forced 从 settings::view 递下来,于是 --force-state 能拍到这一处 - language / theme / appearance choice 三处「选中档位」补状态层 - components/selection/paint/chip.rs:选中分支用上 state 参数 - shell/nav.rs / shell/nav/account.rs:两处私有拷贝收口到 selected_fill(几何没动) - components/navigation/tabs.rs:状态层在样式闭包里跟 button::Status 求值 - 新增门禁 selection/tests/single_entry.rs:11 个站点必须调 selected_fill + 产品代码里不许再出现 or_else(|| Some(ui.color( 这个私有拷贝形态 - 行数:frame.rs / nav.rs / appearance::section 的单测按「纯搬移」拆出去
This commit is contained in:
1 parent
9b6e92c7b4
commit
0d917e88da
16 files changed
+1072
-355
No files matched your search
@@ -175,20 +175,22 @@ pub(super) fn account_item<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// 头像圆的底色(**唯一**实现点):hover / pressed / 过渡帧都画在这个 Ø40 圆上。
|
||||
/// 头像圆的底色(**几何**的唯一实现点):hover / pressed / 过渡帧都画在这个 Ø40 圆上。
|
||||
///
|
||||
/// 两条分支不许合并(照 `drawer/item.rs` 的 `pill_color`):已登录的头像静息是实色
|
||||
/// `SecondaryContainer`,缺层时兜底到那个角色;未登录的头像静息只有描边、不填底,
|
||||
/// 兜底就必须是 `None`。
|
||||
/// **颜色**只有一个来源:已登录支走 [`interaction::selected_fill`](全仓唯一的选中底色入口,
|
||||
/// M299 收口了这里的私有拷贝),未登录支走 [`interaction::layer_color`]。两条分支不许合并:
|
||||
/// 已登录的头像静息是实色 `SecondaryContainer`(`selected_fill` 的兜底),未登录的头像静息只有
|
||||
/// 描边、不填底(`layer_color` 给 `None`)。
|
||||
fn avatar_color(ui: &Ui, state: Interaction, signed_in: bool) -> Option<Color> {
|
||||
if signed_in {
|
||||
interaction::layer_color(
|
||||
// 同一个唯一入口(M299):已登录头像的静息底是实色 `SecondaryContainer`,
|
||||
// hover / pressed 叠在它上面——此前这里是第五份私有拷贝。
|
||||
Some(interaction::selected_fill(
|
||||
ui,
|
||||
state,
|
||||
Role::OnSecondaryContainer,
|
||||
Role::SecondaryContainer,
|
||||
)
|
||||
.or_else(|| Some(ui.color(Role::SecondaryContainer)))
|
||||
))
|
||||
} else {
|
||||
// 头像直接摆在导航轨上(轨画的是 `MaterialLayer::Canvas`):状态层要叠在同一个角色上。
|
||||
interaction::layer_color(
|
||||
@@ -220,6 +222,42 @@ mod tests {
|
||||
assert!(compacted.compact, "compact(true) 必须收成图标条");
|
||||
}
|
||||
|
||||
/// **已登录头像也跟态走**,且走的是全仓唯一的那个入口(M299)。
|
||||
///
|
||||
/// 抓得住:把 `signed_in` 支改回「选中就不叠层」⇒ hover / pressed 都等于静息,前两条红;
|
||||
/// hover 与 pressed 同源 ⇒ 第三条红。未登录支(只有描边、不填底)一并钉住。
|
||||
/// 抓不住:像素(`DevDocs/artifacts/m299/shell-account-*`);Ø40 几何本次没动。
|
||||
#[test]
|
||||
fn the_signed_in_avatar_reacts_to_hover_and_press() {
|
||||
use unlucky_design::color::SchemeFlavor;
|
||||
use unlucky_design::typography::Platform;
|
||||
|
||||
use crate::theme::Families;
|
||||
|
||||
let ui = Ui::with_platform(
|
||||
SchemeFlavor::Light,
|
||||
Platform::Windows,
|
||||
Families {
|
||||
text: "Segoe UI",
|
||||
display: "Segoe UI",
|
||||
mono: "Consolas",
|
||||
},
|
||||
);
|
||||
let fill = |state| avatar_color(&ui, state, true).expect("已登录静息也有底");
|
||||
let rest = fill(Interaction::Enabled);
|
||||
let hover = fill(Interaction::Hovered);
|
||||
let pressed = fill(Interaction::Pressed);
|
||||
|
||||
assert_eq!(rest, ui.color(Role::SecondaryContainer));
|
||||
assert_ne!(hover, rest, "已登录 + hover 必须与静息不同");
|
||||
assert_ne!(pressed, rest, "已登录 + pressed 必须与静息不同");
|
||||
assert_ne!(hover, pressed, "8% 与 10% 必须能区分");
|
||||
|
||||
// 未登录:静息不填底,hover 才有状态层
|
||||
assert_eq!(avatar_color(&ui, Interaction::Enabled, false), None);
|
||||
assert!(avatar_color(&ui, Interaction::Hovered, false).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_avatar_slot_fits_the_rail() {
|
||||
use unlucky_design::color::SchemeFlavor;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
//! `shell/nav.rs` 的单测(M299 从 `nav.rs` 纯搬移,守 400 行硬线)。
|
||||
//!
|
||||
//! 判据与注释一字未动,只是**换文件**:`nav.rs` 加了一条「选中药丸跟态走」之后到 413 行。
|
||||
//! 模块路径仍是 `shell::nav::tests`(`nav.rs` 里改成 `mod tests;`),`super::*` 拿到的还是
|
||||
//! `shell::nav`。
|
||||
|
||||
use super::*;
|
||||
use unlucky_design::color::SchemeFlavor;
|
||||
use unlucky_design::typography::Platform;
|
||||
|
||||
use crate::theme::Families;
|
||||
|
||||
fn ui() -> Ui {
|
||||
Ui::with_platform(
|
||||
SchemeFlavor::Light,
|
||||
Platform::Windows,
|
||||
Families {
|
||||
text: "Segoe UI",
|
||||
display: "Segoe UI",
|
||||
mono: "Consolas",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indicator_matches_the_reference_56x32() {
|
||||
let (width, height) = indicator_size(LayoutMetrics::DESKTOP);
|
||||
assert_eq!((width, height), (56.0, 32.0), "12b 5.4 的参考值");
|
||||
}
|
||||
|
||||
/// **选中药丸也跟态走**,且走的是全仓唯一的那个入口(M299)。
|
||||
///
|
||||
/// 抓得住:①把选中支改回「选中就不叠层」(`Some(ui.color(SecondaryContainer))`)⇒ hover /
|
||||
/// pressed 都等于静息,前两条红;②把 hover 的 α 抄成 pressed 的 10% ⇒ `hover != pressed` 红;
|
||||
/// ③把选中支的合成底写成画布色(导航轨自己画的是 `MaterialLayer::Canvas`,但选中项的
|
||||
/// 状态层要叠在 `SecondaryContainer` 上)⇒ 后两条逐值断言红。
|
||||
/// 抓不住:像素(`DevDocs/artifacts/m299/shell-*-hover|pressed.png`)与**几何**(56×32 由
|
||||
/// `state_layer_box` / `indicator_size` 那两条钉住,本次一个像素都没动);也抓不住「这一处
|
||||
/// 自己抄了一份**正确**的叠层」——那是 `selection/tests/single_entry.rs` 的接线扫描管的事。
|
||||
#[test]
|
||||
fn the_selected_pill_reacts_to_hover_and_press() {
|
||||
use unlucky_design::color::StateLayer;
|
||||
|
||||
let ui = ui();
|
||||
let fill = |state| pill_color(&ui, state, true).expect("选中项静息也有底");
|
||||
let rest = fill(Interaction::Enabled);
|
||||
let hover = fill(Interaction::Hovered);
|
||||
let pressed = fill(Interaction::Pressed);
|
||||
|
||||
assert_eq!(
|
||||
rest,
|
||||
ui.color(Role::SecondaryContainer),
|
||||
"选中静息 = 实色容器"
|
||||
);
|
||||
assert_ne!(hover, rest, "选中 + hover 必须与静息不同");
|
||||
assert_ne!(pressed, rest, "选中 + pressed 必须与静息不同");
|
||||
assert_ne!(hover, pressed, "8% 与 10% 必须能区分");
|
||||
assert_eq!(
|
||||
hover,
|
||||
ui.state_over(
|
||||
StateLayer::Hover,
|
||||
Role::OnSecondaryContainer,
|
||||
Role::SecondaryContainer
|
||||
),
|
||||
"叠的那一层必须逐位等于令牌求值(不是本地算术)"
|
||||
);
|
||||
assert_eq!(
|
||||
pressed,
|
||||
ui.state_over(
|
||||
StateLayer::Pressed,
|
||||
Role::OnSecondaryContainer,
|
||||
Role::SecondaryContainer
|
||||
)
|
||||
);
|
||||
// 未选中支**没变**:静息不画药丸,三态仍是各自的状态层
|
||||
assert_eq!(pill_color(&ui, Interaction::Enabled, false), None);
|
||||
assert!(pill_color(&ui, Interaction::Hovered, false).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nav_item_height_is_indicator_plus_gap_plus_label() {
|
||||
let ui = ui();
|
||||
let (_, indicator_height) = indicator_size(ui.metrics());
|
||||
// 32 + 4 + 16
|
||||
assert_eq!(nav_item_height(&ui, indicator_height), 52.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_nav_item_has_a_label_and_an_icon() {
|
||||
assert!((4..=6).contains(&NavItem::ALL.len()));
|
||||
for item in NavItem::ALL {
|
||||
assert!(!item.label(Lang::En).is_empty(), "{item:?} 没有文案");
|
||||
assert!(!item.icon().icon().path.is_empty(), "{item:?} 没有图标");
|
||||
}
|
||||
}
|
||||
|
||||
/// **M108**:导航栏上这一项叫 `Versions`(88px 轨放不下全名 `Version management`)。
|
||||
///
|
||||
/// 文案本身走目录(M106 的 `label_key()` + `label(lang)`),这条只守**取值**与**宽度余量**:
|
||||
///
|
||||
/// - **抓得住**:① 有人把 `Key::NavDownload` 的英文改回「下载」或塞回 18 字符的全名
|
||||
/// (88px 轨上会截断/换行,实测全名 ≈101px);② 中文被「为了对称」砍成两个字。
|
||||
/// - **抓不住**:真画出来的截断(那要出图,见 M108 报告里的产品通道与 pages_demo 图);
|
||||
/// 也抓不住 240 展开抽屉(那一档在产品里还不可达)。
|
||||
#[test]
|
||||
fn the_download_item_is_labelled_version_management() {
|
||||
// 88px 轨的余量:中文 4 字 ≈48px、英文短名 ≈45px,都该有明显的余量
|
||||
assert_eq!(NavItem::Download.label(Lang::En), "Versions");
|
||||
assert_eq!(NavItem::Download.label(Lang::ZhCn), "版本管理");
|
||||
for item in NavItem::ALL {
|
||||
let label = item.label(Lang::En);
|
||||
assert!(
|
||||
!label.to_ascii_lowercase().contains("download"),
|
||||
"{item:?} 的英文标签还在说下载:{label}"
|
||||
);
|
||||
assert!(
|
||||
label.chars().count() <= 12,
|
||||
"{item:?} 的标签太长,88px 的轨放不下:{label}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user