- 预设色板原来挤在一行,出图实测被折成「7 + 1」(最后一颗掉到下一行)——改成 「标签 + 出厂」一行、8 颗色块一行;这是有意排版,不是被挤出来的 - src/** 的色值字面量门禁把注释与测试一起扫(那条门禁是对的):文档里的 `#RRGGBB` 实例改成不带 `#` 的描述,超长串用例改用 `TEAL.to_hex()` 拼出来
132 lines
5.7 KiB
Rust
132 lines
5.7 KiB
Rust
//! 强调色的**运行期状态**(M250 ACCENTWIRE):输入原文 → 解析结果 → 生效方案。
|
||
//!
|
||
//! 这一层存在的理由:`appearance.accent_color` 此前是死线——键、输入框、presenter 往返都在,
|
||
//! 但造 tokens 的唯一入口恒走 `DesignTokens::brand(board)`,用户填了色界面没变化。接线要落地
|
||
//! 的不只是「把 hex 喂进 `Scheme::from_accent`」,还有**失败要可见**:
|
||
//!
|
||
//! | 输入 | 生效配色 | `error` | 落盘值 |
|
||
//! |---|---|---|---|
|
||
//! | 空串 | 出厂品牌板(`DesignTokens::brand`) | `None` | `""` |
|
||
//! | `006A5C`(青绿) | `DesignTokens::with_accent`(只换三族) | `None` | `#` + `006A5C` |
|
||
//! | `#12` / `zzzzzz` / 超长串 | **保持上一次合法的值**(不静默回退成出厂色) | `Some(..)` | 上一次合法的值 |
|
||
//!
|
||
//! 「保持上一次合法值」而不是「回退出厂板」是刻意的:回退会让「填错」和「填对但没生效」在屏幕上
|
||
//! 长得一模一样,用户唯一的线索就只剩那条错行——而那正是这里要保证不被丢掉的。
|
||
//!
|
||
//! 解析本身不在这里:`AccentChoice::parse` 是唯一真相(`unlucky_presenter::prefs`),本文件只负责
|
||
//! 把结果接到 `Scheme` 与 UI 上。
|
||
|
||
use unlucky_design::color::{Color, Scheme, SchemeFlavor};
|
||
use unlucky_presenter::prefs::{AccentChoice, AccentError};
|
||
|
||
use crate::theme::accent_scheme;
|
||
|
||
/// 出图矩阵的强调色开关(`ULL_APPEARANCE_ACCENT=#RRGGBB`;产品路径读 `launcher.json`)。
|
||
///
|
||
/// 放在本文件而不是 `state.rs` 的 `ENV_*` 那一族:`state.rs` 正好 400 行(硬线),这一条
|
||
/// 属于强调色自己,跟 `Accent` 放一起也更好找。语义与其它 `ULL_APPEARANCE_*` 一致——只影响这次进程。
|
||
pub const ENV_ACCENT: &str = "ULL_APPEARANCE_ACCENT";
|
||
|
||
/// 强调色的运行期状态(`App` 经 `pages::appearance::State` 持有一份)。
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct Accent {
|
||
/// 输入框的原文(受控控件要它;非法输入也必须原样留着,否则用户看不见自己错在哪)。
|
||
text: String,
|
||
/// 上一次**合法**的解析结果(非法输入不改它 —— 生效色因此不会跳回出厂板)。
|
||
choice: AccentChoice,
|
||
/// 解析出来的强调色(`None` = 出厂板,强调色由品牌色板决定)。
|
||
color: Option<Color>,
|
||
/// 非法输入的结构化原因(`None` = 没填或填得合法)。
|
||
error: Option<AccentError>,
|
||
/// 合法自定义色时的方案(`Ui` 直接用它;`None` = 走出厂品牌板)。
|
||
scheme: Option<Scheme>,
|
||
}
|
||
|
||
impl Accent {
|
||
/// 从配置读一份(启动时调一次)。配置里存着非法值时**也**亮错行,不静默吞掉。
|
||
#[must_use]
|
||
pub fn new(text: &str, flavor: SchemeFlavor) -> Self {
|
||
let mut accent = Self {
|
||
text: text.to_owned(),
|
||
choice: AccentChoice::Factory,
|
||
color: None,
|
||
error: None,
|
||
scheme: None,
|
||
};
|
||
accent.retry(flavor);
|
||
accent
|
||
}
|
||
|
||
/// 设置页改了一次输入(受控输入框:每敲一个字符都走这里)。
|
||
pub fn set(&mut self, text: &str, flavor: SchemeFlavor) {
|
||
self.text = text.to_owned();
|
||
self.retry(flavor);
|
||
}
|
||
|
||
/// 配置被外部改写后重置(热切:CLI / AI 改完 1 秒内生效)。
|
||
pub fn reset(&mut self, text: &str, flavor: SchemeFlavor) {
|
||
*self = Self::new(text, flavor);
|
||
}
|
||
|
||
/// 深浅切了:强调族要按新模式重派生(表面色仍由 `State::refresh_palette` 那条路管)。
|
||
pub fn set_flavor(&mut self, flavor: SchemeFlavor) {
|
||
self.scheme = self.color.map(|accent| accent_scheme(flavor, accent));
|
||
}
|
||
|
||
/// 输入框原文。
|
||
#[must_use]
|
||
pub fn text(&self) -> &str {
|
||
&self.text
|
||
}
|
||
|
||
/// 结构化失败原因(`None` = 合法)。
|
||
#[must_use]
|
||
pub fn error(&self) -> Option<AccentError> {
|
||
self.error
|
||
}
|
||
|
||
/// 解析出来的强调色(`None` = 出厂板;日志与截图判据读它)。
|
||
#[must_use]
|
||
pub fn color(&self) -> Option<Color> {
|
||
self.color
|
||
}
|
||
|
||
/// 生效的强调色方案(`Ui` 的唯一入口:`Some` ⇒ 走 [`crate::theme::Ui::with_accent`] 的同一条路)。
|
||
#[must_use]
|
||
pub fn scheme(&self) -> Option<&Scheme> {
|
||
self.scheme.as_ref()
|
||
}
|
||
|
||
/// 落盘值:`""` = 出厂,`#RRGGBB` = 自定义。**永远是上一次合法值**——非法手输不写盘。
|
||
#[must_use]
|
||
pub fn persist_value(&self) -> &str {
|
||
self.choice.as_str()
|
||
}
|
||
|
||
/// 取证日志里的一个词:`factory` / 规范化的 hex / `invalid(Short { digits: 2 })`。
|
||
///
|
||
/// 为什么日志要带这个:截图与真机复现只能靠一行文字分辨「强调色没生效」与「强调色生效了但
|
||
/// 图上看不出来」——后者是这条 mission 的验收判据,前者是它的 bug。
|
||
#[must_use]
|
||
pub fn log_token(&self) -> String {
|
||
match self.error {
|
||
Some(error) => format!("invalid({error:?})"),
|
||
None if self.choice.as_str().is_empty() => "factory".to_owned(),
|
||
None => self.persist_value().to_owned(),
|
||
}
|
||
}
|
||
|
||
/// 按当前 `text` 重算:合法 ⇒ 换方案 + 清错;非法 ⇒ **方案不动** + 记错。
|
||
fn retry(&mut self, flavor: SchemeFlavor) {
|
||
match AccentChoice::parse(&self.text) {
|
||
Ok(choice) => {
|
||
self.color = Color::from_hex(choice.as_str());
|
||
self.scheme = self.color.map(|accent| accent_scheme(flavor, accent));
|
||
self.choice = choice;
|
||
self.error = None;
|
||
}
|
||
Err(error) => self.error = Some(error),
|
||
}
|
||
}
|
||
}
|