diff --git a/crates/app-iced/examples/pages_demo.rs b/crates/app-iced/examples/pages_demo.rs index 34122bf..f4fd806 100644 --- a/crates/app-iced/examples/pages_demo.rs +++ b/crates/app-iced/examples/pages_demo.rs @@ -21,7 +21,9 @@ fn main() -> ExitCode { eprintln!( "用法:pages_demo [--screen=instances|download|settings|logs] [--width ] \ [--height ] [--theme=light|dark] [--screenshot-theme=light|dark] \ - [--screenshot ] [--renderer=auto|gpu|cpu] [--software-renderer]" + [--screenshot ] [--renderer=auto|gpu|cpu] [--software-renderer] \ + [--scale-factor ] [--appearance=real|demo] [--passthrough=on|off] \ + [--force-state=rest|hover|pressed]" ); return ExitCode::from(2); } diff --git a/crates/app-iced/src/pages/harness.rs b/crates/app-iced/src/pages/harness.rs index d0d1a32..4ca179d 100644 --- a/crates/app-iced/src/pages/harness.rs +++ b/crates/app-iced/src/pages/harness.rs @@ -3,7 +3,8 @@ //! **刻意不接 `cli.rs`**:等 `cli.rs` 具备 `--screen` / `--window-width` 之后,这个 harness 应当 //! 删掉,改由正式启动参数出图。参数:`--screen=instances|download|settings|logs`、`--width`、 //! `--height`、`--theme` / `--screenshot-theme`(后者优先)、`--screenshot`、`--renderer`、 -//! `--software-renderer`、`--scale-factor`、`--appearance=real|demo`、`--passthrough=on|off`。 +//! `--software-renderer`、`--scale-factor`、`--appearance=real|demo`、`--passthrough=on|off`、 +//! `--force-state=rest|hover|pressed`(M285:把控件钉到那一态出图,`rest` / 不给时截图通道默认就是它)。 //! 解析与默认值在 [`request`](M100 拆出去:本文件当时正好 400 行、零余量);没给 `--screenshot` //! 就只是开窗看。 //! @@ -40,9 +41,17 @@ //! 改成 900×600,落盘的就是 900×600 那张)。现在闸门在 [`should_request_capture`],日志里的 //! `落盘次数=` 必须恒为 1。 //! -//! **为什么不是给每个控件灌 forced**:设置页「外观」区的滑块 / 文本框 / 裸 `iced::button` 都不 -//! 消费 forced,那样做要改十几个组件文件——为取证去动产品组件,代价与风险都不对,而且新产品 -//! 组件随时会漏。 +//! **forced 能到哪儿(M285 订正,原句已过时)**:`--force-state` 只改**画出来的**态,不改状态机。 +//! 它经 `pages::Model.forced` → `settings::view(.., forced)` → `Group.body` 递到各控件: +//! **裸 `iced::button` 已经消费**——外观区 11 处 `choice` + 语言 / 主题两类 chip + 日志两颗 +//! `action_button`,一律走 `settings::resolve_state`;`components::buttons::Button` 那三组 +//! (update / ai / java)走 `settings::forced_button` 的 `.state(..)`。**仍然不消费的**:外观区的 +//! 滑块 / 文本框 / 8 颗预设色块(`accent::swatch` 的描边闭包没有 forced 入口)——那几处要接, +//! 得先给对应的组件开口,不在本通道的账上。 +//! +//! ⚠️ 上一段在本 mission 之前写的是「裸 `iced::button` 都不消费 forced」——**那句正是 M282/M284 +//! 放弃像素验收的理由**,已经不成立:这四类的 hover / pressed 现在出得来像素证据(判据见 +//! M285 的 `DevDocs/artifacts/forcedwire/`)。别把它再改回去。 //! //! ```text //! cargo run -p unlucky-app-iced --example pages_demo -- --screen=instances --width=1180 \ diff --git a/crates/app-iced/src/pages/settings/mod.rs b/crates/app-iced/src/pages/settings/mod.rs index c686b09..0dad606 100644 --- a/crates/app-iced/src/pages/settings/mod.rs +++ b/crates/app-iced/src/pages/settings/mod.rs @@ -178,11 +178,11 @@ pub(super) fn resolve_state(forced: Option, status: button::Status) } /// 把一个 `components::buttons::Button` 钉到 `forced` 那一态(`pages/instances.rs` 的 `forced_state` -/// 在设置页的这一份)。 +/// 在设置页的这一份):`Some` ⇒ `.state(..)`,`None` ⇒ 原样返回。 /// -/// **这是第二份,不是抽象**(M285):实例页的控件本身就是 `components::buttons::Button`(有 -/// `state(Interaction)`),设置页的 `choice` / `chip` / `action_button` 是裸 `iced::button`(走 -/// [`resolve_state`])。三次法则现在到 2;第三次真出现时落点是 `components/interaction/`,不是这里。 +/// ⚠️ **优先级是 `state` 赢**(`Button::forced()` = `self.state.or(没有消息 ⇒ Disabled)`,`state` 在前) +/// ⇒ `forced_button(Button::disabled(..), Some(Hovered))` 会把禁用按钮**画成 hover**。今天 7 个站点都 +/// 写在**启用分支里**所以安全,**别**套到 `if..else` 外面。第二份不是抽象:三次法则到 2。 pub(super) fn forced_button<'a, M>( button: Button<'a, M>, forced: Option, @@ -352,16 +352,10 @@ fn section_columns<'a>( mod resolve_tests { use super::*; - /// `forced` 必须**覆盖**观测到的 status;不给 `forced` 时必须**退回**观测值(M285)。 - /// - /// 抓得住:把 `Some` 分支改成忽略 forced(直接 `Interaction::from_status(status)`)——第一条立刻红, - /// 而那正好等于「`--force-state` 在四个控件上没接线」。 - /// 抓不住:forced 真的画进了 PNG(那是 `pages_demo --force-state=hover|pressed` 的像素判据)。 - /// `forced_button` 只做一件事:`Some` 钉态、`None` 原样(M285)。 - /// - /// `Button::state` 是私有字段、没有 getter,所以断 **derived Debug** 里的那一格。 - /// 抓得住:`Some` 分支被改成恒 `None`(第一条红);`None` 分支也顺手 `.state(..)`(第二条红)。 - /// 抓不住:按钮真画成那色(那是出图的事)。 + /// 两条语义各钉一次(M285):`forced_button` 的 `Some` 钉态 / `None` 原样(**禁用态也会被 `state` + /// 盖掉**——`Button::forced()` 里 `state` 在 disabled 默认之前,钉住实际行为,别让站点踩)、 + /// `resolve_state` 的 `Some` 覆盖观测 status / `None` 退回观测值。`Button::state` 没有 getter, + /// 所以断 derived Debug 的那一格。抓不住:按钮真画成那色(出图负责)、站点传错 forced(只有像素)。 #[test] fn forced_button_forces_only_when_asked() { let plain = Button::new("x", ()); @@ -378,6 +372,13 @@ mod resolve_tests { untouched.contains("state: None"), "没给 forced 时必须原样返回:{untouched}" ); + // 禁用按钮同样会被钉态(`state` 覆盖 disabled 默认,类型层不挡): + let disabled: Button<'_, ()> = + forced_button(Button::disabled("x"), Some(Interaction::Hovered)); + assert!( + format!("{disabled:?}").contains("state: Some(Hovered)"), + "state 在 disabled 默认之前 ⇒ 禁用按钮也会被画成 hover/pressed" + ); } #[test]