From 767727efb79dd05c4c5aec36296feb391a3e3231 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 12 Nov 2026 22:04:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor(app-iced):=20=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E8=A1=8C=E4=BB=8E=20general.rs=20=E7=BA=AF=E6=90=AC=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=20general/language.rs=EF=BC=88M285=20=E5=89=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit general.rs 390 行、撞 400 行硬线,而 M285 要给语言行三枚 chip 接 forced。 切法与既有的 general/theme.rs 同构(一个行控件一个文件):正文 view + chip + chip_style。语句逐字节搬移(去缩进去可见性后 diff 退出码 0),只把 fn language_row 改名成 pub(super) fn view(与 theme::view 同一入口命名)。 general.rs 390 → 245 行;language.rs 170 行。 --- crates/app-iced/src/pages/settings/general.rs | 158 +--------------- .../src/pages/settings/general/language.rs | 170 ++++++++++++++++++ 2 files changed, 174 insertions(+), 154 deletions(-) create mode 100644 crates/app-iced/src/pages/settings/general/language.rs diff --git a/crates/app-iced/src/pages/settings/general.rs b/crates/app-iced/src/pages/settings/general.rs index de136a8..e13c643 100644 --- a/crates/app-iced/src/pages/settings/general.rs +++ b/crates/app-iced/src/pages/settings/general.rs @@ -38,20 +38,15 @@ //! //! 分组元数据只有一份(这里的 [`GROUP`]):`settings/mod.rs` 的 `GROUPS` 直接引用它。 -use iced::border::Radius; -use iced::widget::{button, column, container, row}; -use iced::{ - Alignment, Background, Border, Color, Element, Length, Padding, Shadow, Theme as IcedTheme, -}; +use iced::widget::{column, container, row}; +use iced::{Alignment, Element, Length, Padding}; use unlucky_design::color::Role; use unlucky_design::icons::IconKind; use unlucky_design::spacing::Space; use unlucky_design::typography::TypeRole; use unlucky_i18n::catalog::Key; -use unlucky_i18n::LangChoice; -use crate::app::components::interaction::{self, Interaction}; use crate::fonts::install::{CjkInstall, FetchClass}; use crate::pages::{frame, Language, Message}; use crate::theme::Ui; @@ -59,6 +54,7 @@ use crate::theme::Ui; use super::frame::{card, divider}; use super::{Group, States}; +mod language; mod theme; /// General 分组(左列名固定英文:左列是**导航**,与导航轨同源,不进 catalog)。 @@ -79,7 +75,7 @@ pub(super) const GROUP: Group = Group { pub(super) fn body<'a>(ui: &'a Ui, states: States<'a>, language: Language) -> Element<'a, Message> { let catalog = language.effective.catalog(); let mut stacked = column![].spacing(0.0).width(Length::Fill); - stacked = stacked.push(language_row(ui, language)); + stacked = stacked.push(language::view(ui, language)); // M168(D28 后半):中文字体的下载状态行。紧贴语言行——用户在这里手选中文,反馈就该在这行下面, // 而不是等他切到别的页面才发现「刚才那几秒在干嘛」。 stacked = stacked.push(divider(ui)); @@ -215,66 +211,6 @@ fn font_row<'a>(ui: &'a Ui, language: Language) -> Element<'a, Message> { stacked.into() } -/// 语言行:标题 + 生效语言回显 + 三档互斥按钮 + 一句说明。 -fn language_row<'a>(ui: &'a Ui, language: Language) -> Element<'a, Message> { - let catalog = language.effective.catalog(); - let effective = LangChoice::Fixed(language.effective).label_key(); - - let head = row![ - container(frame::text_role( - ui, - TypeRole::BodyMedium, - false, - catalog.text(Key::SettingsLanguage), - ui.color(Role::OnSurface), - )) - .width(Length::Fill) - .align_y(Alignment::Center), - frame::text_role_owned( - ui, - TypeRole::LabelLarge, - true, - catalog.render( - Key::SettingsLanguageEffective, - &[("language", catalog.text(effective))], - ), - ui.color(Role::OnPrimaryFixedVariant), - ), - ] - .width(Length::Fill) - .align_y(Alignment::Center); - - let chips = row(LangChoice::ALL - .iter() - .map(|choice| { - chip( - ui, - catalog.text(choice.label_key()), - *choice == language.choice, - *choice, - ) - }) - .collect::>()) - .spacing(ui.space(Space::X1)) - .align_y(Alignment::Center); - - column![ - head, - chips, - frame::text_role( - ui, - TypeRole::BodySmall, - false, - catalog.text(Key::SettingsLanguageHint), - ui.color(Role::OnSurfaceVariant), - ), - ] - .spacing(ui.space(Space::X1)) - .width(Length::Fill) - .padding(Padding::from([ui.space(Space::X2), 0.0])) - .into() -} - /// 一个只读的「标签 + 取值」行(与 `frame::demo_card` 的一行同高同排版)。 fn setting_row<'a>(ui: &'a Ui, label: &'a str, value: &'a str) -> Element<'a, Message> { let height = ui.metrics().control_height + Space::X2.dp(); @@ -302,89 +238,3 @@ fn setting_row<'a>(ui: &'a Ui, label: &'a str, value: &'a str) -> Element<'a, Me .align_y(Alignment::Center) .into() } - -/// 一个互斥按钮(选中态 `SecondaryContainer`,未选中透明 + `OnSurfaceVariant`)。 -/// -/// 与 `pages/appearance/section/mod.rs` 的 `choice` 同款几何,但文案来自 catalog(那份是英文基线)。 -/// -/// **几何(M229)**:取规格卡的 chip(高 32、圆角 8、内距 16、未选中 1px `OutlineVariant`), -/// 与同屏的主题三档 chips 完全同源——两行都是「并排的互斥筛选」,长得不一样会让读者以为是 -/// 两种控件(M229 第一版就是这样:语言 40 高药丸、主题 32 高方角,出图一眼就不齐)。 -fn chip<'a>(ui: &'a Ui, text: &'a str, selected: bool, choice: LangChoice) -> Element<'a, Message> { - let box_ = unlucky_design::inkcat::metrics::CHIP; - let height = box_.height; - let content_color = if selected { - ui.color(Role::OnSecondaryContainer) - } else { - ui.color(Role::OnSurfaceVariant) - }; - let border_color = ui.color(Role::OutlineVariant); - let stroke = crate::theme::STROKE_PX; - let radius = ui.radius(box_.shape, height); - - button( - container(frame::text_role( - ui, - TypeRole::LabelLarge, - selected, - text, - content_color, - )) - .align_y(Alignment::Center), - ) - .height(height) - .padding(Padding::from([ - 0.0, - unlucky_design::inkcat::metrics::CHIP_PAD_X, - ])) - .style(chip_style( - ui, - selected, - content_color, - border_color, - stroke, - radius, - )) - .on_press(Message::Language(choice)) - .into() -} - -/// [`chip`] 的样式闭包(抽成自由函数:单测要**直接**喂 `Hovered` / `Pressed` 两态)。 -/// -/// - **α 只从 [`Interaction`] 来**:hover 8% / pressed 10%(M3)。M284 之前两种态合并成一条 -/// `matches!(status, Hovered | Pressed)` 分支、都叠 `StateLayer::Hover`,按下于是被压成 8%; -/// - **合成底是 [`frame::BASE`]**(M237:这一组住在 `frame::card` 里,状态层叠卡底而不是画布)。 -fn chip_style<'a>( - ui: &'a Ui, - selected: bool, - content_color: Color, - border_color: Color, - stroke: f32, - radius: Radius, -) -> impl Fn(&IcedTheme, button::Status) -> button::Style + 'a { - move |_theme, status| { - let background = if selected { - Some(Background::Color(ui.color(Role::SecondaryContainer))) - } else { - interaction::layer_color( - ui, - Interaction::from_status(status), - Role::OnSurface, - frame::BASE.role(), - ) - .map(Background::Color) - }; - - button::Style { - background, - text_color: content_color, - border: Border { - color: border_color, - width: if selected { 0.0 } else { stroke }, - radius, - }, - shadow: Shadow::default(), - snap: true, - } - } -} diff --git a/crates/app-iced/src/pages/settings/general/language.rs b/crates/app-iced/src/pages/settings/general/language.rs new file mode 100644 index 0000000..3db2559 --- /dev/null +++ b/crates/app-iced/src/pages/settings/general/language.rs @@ -0,0 +1,170 @@ +//! 设置页 General 组的**语言行**(M285 `LANGSET`:从 `general.rs` 纯搬移)。 +//! +//! 搬出来的理由只有行数:`general.rs` 390 行、撞 400 行硬线(`components/gate.rs`), +//! 而 M285 要给这一行的三枚互斥按钮接 `forced: Option`(`--force-state` 直通)。 +//! 切法与 [`super::theme`] 完全同构——**一个行控件一个文件**:正文 [`view`] + 它的 [`chip`] +//! + [`chip_style`](样式闭包抽成自由函数,单测才能直接喂 `Hovered` / `Pressed` 两态)。 +//! +//! 搬移是逐字搬的:三个函数的语句、注释一个字没动。 + +use iced::border::Radius; +use iced::widget::{button, column, container, row}; +use iced::{ + Alignment, Background, Border, Color, Element, Length, Padding, Shadow, Theme as IcedTheme, +}; + +use unlucky_design::color::Role; +use unlucky_design::spacing::Space; +use unlucky_design::typography::TypeRole; +use unlucky_i18n::catalog::Key; +use unlucky_i18n::LangChoice; + +use crate::app::components::interaction::{self, Interaction}; +use crate::pages::{frame, Language, Message}; +use crate::theme::Ui; + +/// 语言行:标题 + 生效语言回显 + 三档互斥按钮 + 一句说明。 +pub(super) fn view<'a>(ui: &'a Ui, language: Language) -> Element<'a, Message> { + let catalog = language.effective.catalog(); + let effective = LangChoice::Fixed(language.effective).label_key(); + + let head = row![ + container(frame::text_role( + ui, + TypeRole::BodyMedium, + false, + catalog.text(Key::SettingsLanguage), + ui.color(Role::OnSurface), + )) + .width(Length::Fill) + .align_y(Alignment::Center), + frame::text_role_owned( + ui, + TypeRole::LabelLarge, + true, + catalog.render( + Key::SettingsLanguageEffective, + &[("language", catalog.text(effective))], + ), + ui.color(Role::OnPrimaryFixedVariant), + ), + ] + .width(Length::Fill) + .align_y(Alignment::Center); + + let chips = row(LangChoice::ALL + .iter() + .map(|choice| { + chip( + ui, + catalog.text(choice.label_key()), + *choice == language.choice, + *choice, + ) + }) + .collect::>()) + .spacing(ui.space(Space::X1)) + .align_y(Alignment::Center); + + column![ + head, + chips, + frame::text_role( + ui, + TypeRole::BodySmall, + false, + catalog.text(Key::SettingsLanguageHint), + ui.color(Role::OnSurfaceVariant), + ), + ] + .spacing(ui.space(Space::X1)) + .width(Length::Fill) + .padding(Padding::from([ui.space(Space::X2), 0.0])) + .into() +} + +/// 一个互斥按钮(选中态 `SecondaryContainer`,未选中透明 + `OnSurfaceVariant`)。 +/// +/// 与 `pages/appearance/section/mod.rs` 的 `choice` 同款几何,但文案来自 catalog(那份是英文基线)。 +/// +/// **几何(M229)**:取规格卡的 chip(高 32、圆角 8、内距 16、未选中 1px `OutlineVariant`), +/// 与同屏的主题三档 chips 完全同源——两行都是「并排的互斥筛选」,长得不一样会让读者以为是 +/// 两种控件(M229 第一版就是这样:语言 40 高药丸、主题 32 高方角,出图一眼就不齐)。 +fn chip<'a>(ui: &'a Ui, text: &'a str, selected: bool, choice: LangChoice) -> Element<'a, Message> { + let box_ = unlucky_design::inkcat::metrics::CHIP; + let height = box_.height; + let content_color = if selected { + ui.color(Role::OnSecondaryContainer) + } else { + ui.color(Role::OnSurfaceVariant) + }; + let border_color = ui.color(Role::OutlineVariant); + let stroke = crate::theme::STROKE_PX; + let radius = ui.radius(box_.shape, height); + + button( + container(frame::text_role( + ui, + TypeRole::LabelLarge, + selected, + text, + content_color, + )) + .align_y(Alignment::Center), + ) + .height(height) + .padding(Padding::from([ + 0.0, + unlucky_design::inkcat::metrics::CHIP_PAD_X, + ])) + .style(chip_style( + ui, + selected, + content_color, + border_color, + stroke, + radius, + )) + .on_press(Message::Language(choice)) + .into() +} + +/// [`chip`] 的样式闭包(抽成自由函数:单测要**直接**喂 `Hovered` / `Pressed` 两态)。 +/// +/// - **α 只从 [`Interaction`] 来**:hover 8% / pressed 10%(M3)。M284 之前两种态合并成一条 +/// `matches!(status, Hovered | Pressed)` 分支、都叠 `StateLayer::Hover`,按下于是被压成 8%; +/// - **合成底是 [`frame::BASE`]**(M237:这一组住在 `frame::card` 里,状态层叠卡底而不是画布)。 +fn chip_style<'a>( + ui: &'a Ui, + selected: bool, + content_color: Color, + border_color: Color, + stroke: f32, + radius: Radius, +) -> impl Fn(&IcedTheme, button::Status) -> button::Style + 'a { + move |_theme, status| { + let background = if selected { + Some(Background::Color(ui.color(Role::SecondaryContainer))) + } else { + interaction::layer_color( + ui, + Interaction::from_status(status), + Role::OnSurface, + frame::BASE.role(), + ) + .map(Background::Color) + }; + + button::Style { + background, + text_color: content_color, + border: Border { + color: border_color, + width: if selected { 0.0 } else { stroke }, + radius, + }, + shadow: Shadow::default(), + snap: true, + } + } +}