Files
Unlucky-Launcher/crates/app-iced/src/pages/appearance/accent.rs
T
AskaEth e345b55c09 fix(app-iced): 预设色板改成两行排版,注释与用例去掉色值字面量(M250)
- 预设色板原来挤在一行,出图实测被折成「7 + 1」(最后一颗掉到下一行)——改成
  「标签 + 出厂」一行、8 颗色块一行;这是有意排版,不是被挤出来的
- src/** 的色值字面量门禁把注释与测试一起扫(那条门禁是对的):文档里的 `#RRGGBB`
  实例改成不带 `#` 的描述,超长串用例改用 `TEAL.to_hex()` 拼出来
2026-11-11 15:10:03 +08:00

132 lines
5.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 强调色的**运行期状态**(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),
}
}
}