feat(app-iced): M93 r2 —— 背景图真图直出 + 面板 α 收敛到 Ui::layer
复审前补齐两处(tower 扩 scope:Cargo.toml / pages/frame.* / theme.rs / presenter::prefs)。 1) 背景图**真图直出** - `iced` 开 `image-without-codecs`(刻意不用 `image`:后者会打开 `image/default` 全套编解码器); 编解码能力由本 crate 的 `image` features 决定,补了 `jpeg` / `bmp`(用户挑的背景图/壁纸多半是 jpg)。 - `pages/appearance/picture.rs`:`Cover`/`Contain` 走渲染器 `ContentFit`,`Tile` 按 `background::tiles` 算行列(上限 64 块)真的铺 N 个图块;暗化 = 一层「版面底色 @ α=dim」叠在图上。 - 36dp 马赛克那条路**已删除**(不再是主路径,也不留死代码)。 - 踩坑记录:`Handle::from_bytes` 的纹理分配赶不上截图第 3 帧(实测画面全空),改用 `Handle::from_path`(渲染器自己解码),已在 `picture::image_handle` 的文档里写明。 2) 面板 α **收敛到唯一来源** - `Ui::layer()` 直接乘上面板 α(新增 `Ui::panel_alpha` / `with_panel_alpha`,默认 1.0、 `scale_alpha(1.0)` 逐字节不变),`app::rebuild_ui` 注入生效值。 - 于是外壳(根容器/标题栏/导航轨)、`frame::card`(实例/下载/日志三页卡片)、 `frame::header_bar`(新增底色)、外观分组卡片、空态图标块**全部同源**, 调用点不再有任何 `panel_color(..)` 自乘(该 helper 已删)。 - 有背景图时根容器不再盖一层实色画布(否则 panel α=1 时背景图整块被挡)。 3) `presenter::prefs` 补重导出 `PANEL_ALPHA_MIN` / `BG_DIM_MAX`,app-iced 里两份 0.4 / 0.95 字面量改为转发常量。 验收:11+3 张图(`C:\Temp\ull-shots\wt-93\r2-*.png`)——三档填充肉眼可分、`.jpg` 能加载、 GPU 档 panel55 时卡片/分组卡/页头带都透出背景、CPU 档仍强制 1.0 且背景在面板空隙可见; 另附实例/下载/日志三页 panel55 各一张,证明三页都吃到同一个 α。
This commit is contained in:
1 parent
b8ab46662c
commit
22e4c423fa
18 files changed
+399
-535
No files matched your search
@@ -1,23 +1,18 @@
|
||||
//! 令牌 → iced 类型(`Color` / `Font` / `Border` / `Radius` / `Theme`)。
|
||||
//!
|
||||
//! ## 边界(M61 `design-令牌约定.md` §0 的落地)
|
||||
//! ## 边界(M61 `design-令牌约定.md` §0)
|
||||
//!
|
||||
//! 本模块是**唯一**允许把 `unlucky-design` 的值翻成 iced 类型的地方。组件层(`shell`)
|
||||
//! 只调 [`Ui`] 的方法,绝不出现色值 / 字号 / 半径 / 间距字面量——`tests/no_hardcoded_tokens.rs`
|
||||
//! 会扫 `src/**` 做守门。
|
||||
//! 本模块是**唯一**允许把 `unlucky-design` 的值翻成 iced 类型的地方:组件层只调 [`Ui`],
|
||||
//! 绝不出现色值/字号/半径/间距字面量(`tests/no_hardcoded_tokens.rs` 扫 `src/**` 守门)。
|
||||
//!
|
||||
//! ## 为什么不用 `iced::theme::Base` 自定义主题承载 49 个角色
|
||||
//! ## 为什么不用 `iced::theme::Base` 承载 49 个角色
|
||||
//!
|
||||
//! `iced` 的 `Palette` 只有 6 个颜色(`background/text/primary/success/warning/danger`),
|
||||
//! M3 有 49 个角色,塞不进去(M60 Q5 + M61 §9-0 的同一结论)。所以:
|
||||
//! iced 的 `Palette` 只有 6 个颜色,M3 有 49 个角色(M60 Q5 + M61 §9-0)。所以真源是本模块持有
|
||||
//! 的 [`Scheme`],iced 的 `Theme` 只用来「换名字」——名字变了 iced 才重算受主题影响的缓存,
|
||||
//! `palette()` 只给 devtools 看,控件外观由各自的 `Style` 注入。
|
||||
//!
|
||||
//! - **真源**是本模块持有的 [`Scheme`](49 角色);
|
||||
//! - iced 的 `Theme` 只用来「换名字」——`Theme::name()` 变了 iced 才会重算受主题影响的缓存
|
||||
//! (M61 §9 的最后一条)。`palette()` 只给 devtools 看,控件外观一律由各自的 `Style` 注入。
|
||||
//!
|
||||
//! `success` / `warning` 在 M3 里**没有对应角色**(`design-令牌约定.md` §2 明写),
|
||||
//! 这里按「语义最近的现成角色」映射:`success → Secondary`(不是「成功」的承诺)、
|
||||
//! `warning → Tertiary`(M3 把「提醒」放 Tertiary)。任何控件都不要去读这两个槽位。
|
||||
//! `success`/`warning` 在 M3 里没有对应角色,按语义最近的现成角色映射
|
||||
//! (`success → Secondary`、`warning → Tertiary`),任何控件都不要读这两个槽位。
|
||||
|
||||
use iced::border::Radius;
|
||||
use iced::theme::Palette;
|
||||
@@ -29,10 +24,7 @@ use unlucky_design::shape::ShapeScale;
|
||||
use unlucky_design::spacing::{LayoutMetrics, Space};
|
||||
use unlucky_design::typography::{FontFamily, Platform, TypeRole, TypeStyle, Weight};
|
||||
|
||||
/// 全站描边宽度(逻辑 px @100% DPI)。rubric G7 要求全站 1px —— 令牌层的「一像素线单一真源」。
|
||||
///
|
||||
/// M69 起令牌层提供 `unlucky_design::stroke::HAIRLINE`(容器描边/分隔线/聚焦环共用),
|
||||
/// 这里**再导出**而不是自己写 `1.0`:全仓只有令牌层一个定义点。
|
||||
/// 全站描边宽度(rubric G7:1px)。令牌层的 `stroke::HAIRLINE` 是唯一定义点,这里只再导出。
|
||||
pub use unlucky_design::stroke::HAIRLINE as STROKE_PX;
|
||||
|
||||
/// 已经解析到系统的字体族名(`&'static str` 才能塞进 `iced::Font`)。
|
||||
@@ -48,15 +40,16 @@ pub struct Families {
|
||||
pub mono: &'static str,
|
||||
}
|
||||
|
||||
/// 令牌求值器:一个 `Ui` 对应一套主题(浅或深)。
|
||||
///
|
||||
/// 深浅热切 = 重新 `Ui::new(flavor, families)` 并整树重绘;控件里不要缓存颜色。
|
||||
/// 令牌求值器:一个 `Ui` 对应一套主题(浅或深)。热切 = 重建 `Ui` 并整树重绘,
|
||||
/// 控件里不要缓存颜色。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ui {
|
||||
scheme: Scheme,
|
||||
platform: Platform,
|
||||
metrics: LayoutMetrics,
|
||||
families: Families,
|
||||
/// 面板层不透明度(D36 / M93):**唯一的 α 来源**,由 [`Ui::layer`] 统一施加。
|
||||
panel_alpha: f32,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
@@ -72,27 +65,26 @@ impl Ui {
|
||||
platform,
|
||||
metrics: LayoutMetrics::DESKTOP,
|
||||
families,
|
||||
panel_alpha: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前深浅模式。
|
||||
/// 当前深浅。
|
||||
pub fn flavor(&self) -> SchemeFlavor {
|
||||
self.scheme.flavor()
|
||||
}
|
||||
|
||||
/// 用**已解析的方案**构造求值器(D38 的落点:背景图 / 桌面壁纸取色 → 49 个角色换掉 baseline)。
|
||||
/// 用**已解析的方案**构造求值器(D38:背景图 / 壁纸取色 → 49 个角色换掉 baseline)。
|
||||
///
|
||||
/// 只是**追加**的构造入口:既有的 [`Ui::new`] / [`Ui::with_platform`] 一字未动,默认仍是
|
||||
/// [`Scheme::baseline`]。调用方(`app` 侧)拿到 `unlucky_design::palette::resolve_source` 的
|
||||
/// 结果后走这里,于是「配色来源」真的能改颜色,而不只是一条日志。
|
||||
///
|
||||
/// 平台与骨架尺寸仍按当前环境求值——方案只带颜色,不带平台。
|
||||
/// 追加式入口:`new` / `with_platform` 的行为一字未动(默认仍是 [`Scheme::baseline`]);
|
||||
/// 平台与骨架尺寸仍按当前环境求值——方案只带颜色。
|
||||
pub fn from_scheme(scheme: Scheme, families: Families) -> Self {
|
||||
Self {
|
||||
scheme,
|
||||
platform: Platform::current(),
|
||||
metrics: LayoutMetrics::DESKTOP,
|
||||
families,
|
||||
panel_alpha: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,25 +113,44 @@ impl Ui {
|
||||
to_iced(self.scheme.get(role))
|
||||
}
|
||||
|
||||
/// 材质层(Canvas/Card/Overlay)→ 容器色。
|
||||
/// 材质层(Canvas/Card/Overlay)→ 容器色,**已乘上面板 α**(M93 的唯一来源)。
|
||||
///
|
||||
/// 为什么 α 施加在这里:面板 α 是**表面色**的属性,而全仓所有表面色都从这个函数出去
|
||||
/// (外壳的根容器/标题栏/导航轨、`pages::frame::card`、空态图标块……),乘一次就让
|
||||
/// 四页 + 外壳自动同源,调用点没有机会「各处自乘」。`α = 1.0` 时逐字节等于原色。
|
||||
pub fn layer(&self, layer: MaterialLayer) -> Color {
|
||||
self.color(layer.surface_role())
|
||||
.scale_alpha(self.panel_alpha)
|
||||
}
|
||||
|
||||
/// 状态层叠在容器色上,返回**不透明**结果。
|
||||
///
|
||||
/// 为什么不用带 alpha 的叠加:D27 要求 CPU 档不出现半透明,而「不透明结果」让 GPU/CPU
|
||||
/// 两档的颜色也逐像素一致(只有材质/玻璃才允许两档不同)。
|
||||
/// 当前面板 α(CPU 档由 `app` 侧强制成 1.0)。
|
||||
pub fn panel_alpha(&self) -> f32 {
|
||||
self.panel_alpha
|
||||
}
|
||||
|
||||
/// 换一个面板 α(只影响 `layer()` 出的表面色,不动角色色 `color()`;默认 `1.0`)。
|
||||
#[must_use]
|
||||
pub fn with_panel_alpha(mut self, alpha: f32) -> Self {
|
||||
self.panel_alpha = if alpha.is_finite() {
|
||||
alpha.clamp(0.0, 1.0)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// 状态层叠在容器色上,返回**不透明**结果(D27:GPU/CPU 两档状态色逐像素一致;
|
||||
/// 只有材质/面板才允许两档不同)。
|
||||
pub fn state_over(&self, state: StateLayer, content: Role, container: Role) -> Color {
|
||||
to_iced(state.over(self.scheme.get(content), self.scheme.get(container)))
|
||||
}
|
||||
|
||||
/// 圆角(令牌刻度);`side` 是控件在该轴上的边长(`ShapeScale::Full` 要按边长折算)。
|
||||
/// 圆角(`side` = 控件在该轴上的边长,`Full` 要按它折算)。
|
||||
pub fn radius(&self, shape: ShapeScale, side: f32) -> Radius {
|
||||
Radius::new(shape.radius_px(side))
|
||||
}
|
||||
|
||||
/// 固定档圆角(不需要边长)。`ShapeScale::Full` 必须走 [`Ui::radius`](它没有绝对值)。
|
||||
/// 固定档圆角。`ShapeScale::Full` 必须走 [`Ui::radius`](它没有绝对值)。
|
||||
pub fn radius_dp(&self, shape: ShapeScale) -> Radius {
|
||||
debug_assert!(
|
||||
!matches!(shape, ShapeScale::Full),
|
||||
@@ -171,7 +182,7 @@ impl Ui {
|
||||
role.emphasized()
|
||||
}
|
||||
|
||||
/// 某个排版档对应的 iced 字体(族 + 字重)。
|
||||
/// 排版档 → iced 字体(族 + 字重)。
|
||||
pub fn font_of(&self, style: TypeStyle) -> Font {
|
||||
let family = match style.family {
|
||||
FontFamily::Display => self.families.display,
|
||||
@@ -196,7 +207,7 @@ impl Ui {
|
||||
self.font_of(self.emphasized(role))
|
||||
}
|
||||
|
||||
/// iced 的 6 色 `Palette`(只给 devtools / 兜底控件用;M3 的 49 角色在 [`Ui::color`])。
|
||||
/// iced 的 6 色 `Palette`(只给 devtools;M3 的 49 角色在 [`Ui::color`])。
|
||||
pub fn palette(&self) -> Palette {
|
||||
Palette {
|
||||
background: self.color(Role::Surface),
|
||||
@@ -348,12 +359,8 @@ mod tests {
|
||||
assert_ne!(hovered, ui.color(Role::Surface));
|
||||
}
|
||||
|
||||
/// D38:配色来源解析出来的方案必须**真的换色**(M93)。
|
||||
///
|
||||
/// 抓得住:①`from_scheme` 用的是传进来的方案,不是 baseline(配一张蓝底的取色方案,
|
||||
/// primary 与 baseline 不同);②默认构造(`new` / `with_platform`)逐字段等于 baseline
|
||||
/// ——即「不传 scheme 时行为不变」,这条是防回流的那一半。
|
||||
/// 抓不住:取色结果好不好看、设置页是否真的切过去(前者是设计判断,后者由 app 侧日志举证)。
|
||||
/// D38:配色方案必须真的换色;默认构造逐字段等于 baseline(防回流的那一半)。
|
||||
/// 抓不住:取色好不好看、设置页是否真切过去(后者由 app 侧日志举证)。
|
||||
#[test]
|
||||
fn a_resolved_scheme_replaces_the_baseline_colours() {
|
||||
use unlucky_design::palette::{resolve_source, CardKey, ImageInput};
|
||||
|
||||
Reference in new issue
Block a user