fix(app-iced): 面板预算按内容推导 + 版本菜单不画 L2 阴影(M238)
两条都在「视口不终止内容」这一族里,修法各一半: ① 三栏档面板预算从死的 `PANEL_BUDGET = 300` 改成 `panel_budget(menu_items)` (内边距 24 + 首屏 76 + 间距 8 + 钉死区 88 + 菜单高 + 列表↔面板 21)。 旧常数不含列表↔面板那 21、也不随版本菜单行数变,于是 760/900 档面板实得 286/294px,滚动视口被挤到 54/62px——六枚 loader chip 只露出 10px(736 与 1180 同样严重,纵向预算与窗宽无关)。修后 760 档视口 98px(chips 31px 整枚在屏)、 900 档 106px;列表按整行让出:760 档 4→3 整行、900 档 7→6 整行;只有一条 加载器版本时菜单只占 64px,列表仍是 4 整行。 ② 版本菜单改用 `Menu::flat()`:不是「把阴影裁掉」,而是**不在滚动容器里画阴影**。 `iced_tiny_skia` 0.14 画 quad 阴影走 `draw_pixmap(.., mask: None)` (engine.rs:136-143),绕过裁切掩码;`iced_wgpu` 那边是整层 scissor (quad.rs:96-101)⇒ 同一棵树两个后端画出来不一样。修前菜单底下 54px 一条被压暗 (浅色 249→231,−7.2%,含 8px 缝与实例名框内上半部),修后逐像素等于画布色。 菜单的 L2 令牌不动,真实浮层照旧带阴影。 判据(两条,抓得住的/抓不住的都写进文档注释): - 结构:`rows/tail.rs` 断言三栏档 760/900 的滚动视口 ≥ 首屏 76px(对生产常量做算术); - 像素:`options_shot/compact.rs` 断言 chip 描边上下两行都在、且实例名框上方那条缝 与框内都是纯画布色。 变异自证:预算退回 300 ⇒ 结构断言红("滚动视口只有 54px < 首屏 76px");阴影无视 `flat()` ⇒ 像素断言红("y=660 x=304 是 [231,231,232] 而不是画布色")。 没验:滚动中/底部三态拍不出来(harness 无 `scroll_to` 派发通道,已另开 finding)。
This commit is contained in:
1 parent
dbc5274965
commit
df0944894f
11 files changed
+454
-69
No files matched your search
@@ -19,6 +19,10 @@
|
||||
//! **容器色不取 `MaterialLayer::Overlay`**:那是 `SurfaceContainerHigh`,比 A1 的 `SurfaceContainer`
|
||||
//! 高一档;`MaterialLayer` 只用来确认「弹出层属于 Overlay 语义」,颜色以 A1 为准。
|
||||
//!
|
||||
//! 阴影不是所有用法都要:被放进**滚动视口**(会被裁切)的菜单要用 [`Menu::flat`]——
|
||||
//! CPU 后端画阴影时绕过裁切掩码,阴影会漏到视口外压暗下面的内容(M238,理由与实证见
|
||||
//! `menu/shadow.rs` 的 `flat` 文档)。
|
||||
//!
|
||||
//! ## 分组分隔线
|
||||
//!
|
||||
//! 复用 [`crate::app::components::divider::full`](一像素线 + `OutlineVariant`),**不自己画线**。
|
||||
@@ -26,9 +30,7 @@
|
||||
|
||||
use iced::widget::text::LineHeight;
|
||||
use iced::widget::{button, column, container, text};
|
||||
use iced::{
|
||||
Alignment, Background, Border, Color, Element, Length, Padding, Pixels, Shadow, Vector,
|
||||
};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Pixels, Shadow};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
use unlucky_design::component_map::Component;
|
||||
@@ -46,6 +48,10 @@ use super::super::interaction::{self, Base, Interaction};
|
||||
use super::super::layout;
|
||||
use super::geometry::MenuGeometry;
|
||||
|
||||
mod shadow;
|
||||
|
||||
use shadow::panel_shadow;
|
||||
|
||||
/// 菜单里的一项。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MenuItem<'a, M> {
|
||||
@@ -121,6 +127,7 @@ pub enum Entry<'a, M> {
|
||||
pub struct Menu<'a, M> {
|
||||
entries: Vec<Entry<'a, M>>,
|
||||
width: Length,
|
||||
shadow: bool,
|
||||
}
|
||||
|
||||
impl<'a, M> Default for Menu<'a, M> {
|
||||
@@ -135,6 +142,7 @@ impl<'a, M> Menu<'a, M> {
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
width: Length::Fill,
|
||||
shadow: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +223,7 @@ fn item_palette(ui: &Ui, base: Base, state: Interaction, selected: bool) -> Item
|
||||
/// 项的禁用态(38% 内容)与状态层拿同一个 [`Base`] 合成——两边同源,改色不会只改一半(M234)。
|
||||
pub const PANEL_BASE: Base = Base::of(Role::SurfaceContainer);
|
||||
|
||||
/// 菜单弹出层(`SurfaceContainer` + L2 阴影)。
|
||||
/// 菜单弹出层(`SurfaceContainer` + L2 阴影;[`Menu::flat`] 时无阴影)。
|
||||
pub fn menu<'a, M: Clone + 'a>(ui: &'a Ui, menu: Menu<'a, M>) -> Element<'a, M> {
|
||||
let geometry = MenuGeometry::of(ui);
|
||||
let spec = Component::Menu.spec();
|
||||
@@ -225,7 +233,7 @@ pub fn menu<'a, M: Clone + 'a>(ui: &'a Ui, menu: Menu<'a, M>) -> Element<'a, M>
|
||||
Elevation::Level2,
|
||||
"MenuTokens.ContainerElevation"
|
||||
);
|
||||
let shadow_spec = elevation.shadow();
|
||||
let shadow = panel_shadow(ui, elevation.shadow(), menu.shadow);
|
||||
let panel = ui.color(PANEL_BASE.role());
|
||||
let shape = geometry.shape;
|
||||
|
||||
@@ -246,14 +254,7 @@ pub fn menu<'a, M: Clone + 'a>(ui: &'a Ui, menu: Menu<'a, M>) -> Element<'a, M>
|
||||
radius: ui.radius_dp(shape),
|
||||
..Border::default()
|
||||
},
|
||||
shadow: Shadow {
|
||||
color: Color {
|
||||
a: shadow_spec.alpha,
|
||||
..ui.color(Role::Shadow)
|
||||
},
|
||||
offset: Vector::new(0.0, shadow_spec.offset_y_px),
|
||||
blur_radius: shadow_spec.blur_px,
|
||||
},
|
||||
shadow,
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//! 菜单「要不要画 L2 阴影」(M238)。
|
||||
//!
|
||||
//! 从 `menu.rs` 拆出来只为守 400 行硬线(`components::gate::every_source_file_stays_under_the_line_limit`):
|
||||
//! 那边加完「内联列表口径」正好 400 行、零余量。搬移的是**新增**的两小块,没有改动既有逻辑。
|
||||
|
||||
use iced::{Color, Shadow, Vector};
|
||||
|
||||
use unlucky_design::color::Role;
|
||||
use unlucky_design::elevation::ShadowSpec;
|
||||
|
||||
use crate::theme::Ui;
|
||||
|
||||
use super::Menu;
|
||||
|
||||
impl<'a, M> Menu<'a, M> {
|
||||
/// **内联列表口径**:不画 L2 阴影(容器色 / 圆角 / 项几何照旧)。
|
||||
///
|
||||
/// 用在「菜单被放进**会被裁切的容器**(滚动视口)」的场合——下载页的加载器版本列表就是
|
||||
/// 这一种:它是面板里的一串版本,不是浮在页面上的弹出层。
|
||||
///
|
||||
/// 为什么必须在这里让步而不是「让它裁掉」:`iced_tiny_skia` 0.14 画 quad 阴影时用
|
||||
/// `pixmap.draw_pixmap(.., mask: None)`(`iced_tiny_skia/src/engine.rs:136-143`),**绕过**
|
||||
/// 了 `clip_mask` ⇒ 阴影照画到视口之外,压暗下面钉死区的实例名(M238 实测:浅色画面布
|
||||
/// `(249,249,250)` 被压成 `(231,231,232)`,−7.2%)。`iced_wgpu` 那条路走整层的 scissor
|
||||
/// (`iced_wgpu/src/quad.rs:96-101`)⇒ 同一棵树在 GPU / CPU 两个后端上**画出来的不一样**。
|
||||
/// 不画阴影让两边一致,也把「浮层」语义收回到真正浮起来的地方。
|
||||
///
|
||||
/// 层级观感:`Component::Menu.spec().elevation` 仍是 **L2**(令牌表不动),真实浮层
|
||||
/// (gallery / 将来的下拉)照旧带阴影;只有「内联列表」这一种用法放弃阴影,靠
|
||||
/// `SurfaceContainer` 底色 + 4 圆角与画布区分(L1 卡片 → L2 菜单这条阶梯不被削平)。
|
||||
pub fn flat(mut self) -> Self {
|
||||
self.shadow = false;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// 面板阴影:`elevated` 为假([`Menu::flat`])时给 `Shadow::default()`——**不是**把 α 调小,
|
||||
/// 而是让 tiny-skia 的 `shadow.color.a > 0.0` 一判就直接跳过整块阴影绘制。
|
||||
///
|
||||
/// 单独抽出来是为了让「内联列表不画阴影」这条可断言:`Element` 没有几何/样式内省,
|
||||
/// 单测只能钉这个纯函数(见 `menu/tests.rs`)。
|
||||
pub(super) fn panel_shadow(ui: &Ui, spec: ShadowSpec, elevated: bool) -> Shadow {
|
||||
if !elevated {
|
||||
return Shadow::default();
|
||||
}
|
||||
Shadow {
|
||||
color: Color {
|
||||
a: spec.alpha,
|
||||
..ui.color(Role::Shadow)
|
||||
},
|
||||
offset: Vector::new(0.0, spec.offset_y_px),
|
||||
blur_radius: spec.blur_px,
|
||||
}
|
||||
}
|
||||
@@ -121,3 +121,28 @@ fn menu_shapes_build() {
|
||||
let _: Element<'static, u8> = sample(flavor);
|
||||
}
|
||||
}
|
||||
|
||||
/// M238:`flat()`(内联列表口径)**整块阴影不画**,不是「把 α 调小」。
|
||||
///
|
||||
/// 抓得住:有人把 `flat` 实现成「α × 0.5」或只改 blur(`Shadow{ alpha>0, blur=0 }` 在 tiny-skia 里
|
||||
/// 仍会走阴影分支),以及有人把 `menu()` 里那个 `panel_shadow(.., menu.shadow)` 的第三参写死成
|
||||
/// `true`(那样 flat 菜单又带上阴影、漏回视口外)。
|
||||
/// 抓不住:阴影**画出来**的效果(`Element` 没有样式内省);这条只钉决策函数,
|
||||
/// 画出来的证据是 736 那张像素图(`tests/options_shot/compact.rs`)。
|
||||
#[test]
|
||||
fn flat_menus_skip_the_shadow_instead_of_toning_it_down() {
|
||||
let ui = ui(SchemeFlavor::Light);
|
||||
let spec = Elevation::Level2.shadow();
|
||||
|
||||
let elevated = panel_shadow(&ui, spec, true);
|
||||
assert_eq!(elevated.blur_radius, 6.0);
|
||||
assert_eq!((elevated.offset.x, elevated.offset.y), (0.0, 2.0));
|
||||
assert_eq!(elevated.color.a, 0.14, "浮层:令牌给的 α 原样上身");
|
||||
|
||||
let flat = panel_shadow(&ui, spec, false);
|
||||
assert_eq!(
|
||||
(flat.color.a, flat.blur_radius),
|
||||
(0.0, 0.0),
|
||||
"内联列表:α 与 blur 都归零(tiny-skia 见 α = 0 直接跳过整块阴影,不是淡一点)"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user