refactor(app-iced): M75 r4 —— interaction 拆成 mod.rs(287) + tracker.rs(159),恢复被压掉的文档
interaction.rs 连压三笔才回到 400 行(靠删文档腾空间),而它会被四条家族线各扩一次。 - interaction/mod.rs:五态枚举 + overlay_color / disabled_colors / disabled_content_over(态→颜色) - interaction/tracker.rs:事件→态的状态机 + 它的两条单测;Tracker 用法示例(ignore 代码块)恢复 - 对外路径不变:components::interaction::* 照旧(mod.rs 里再导出 Tracker) - 两份文件都写了「加东西前先看行数余额」 顺带修两处门禁:注释里去掉 hex 色值(守门连注释一起扫)、再导出显式 allow。
This commit is contained in:
1 parent
ab5f5802a8
commit
a878fc6a58
3 files changed
+180
-137
No files matched your search
@@ -111,7 +111,7 @@ pub fn view<'a>(ui: &'a Ui) -> Element<'a, DemoMessage> {
|
||||
.spacing(ui.space(Space::X8))
|
||||
.width(Length::Fill);
|
||||
|
||||
// 滚动条:默认是主题给的 10px 深灰 thumb(`#6A7985`),压过组件本体(视觉 reviewer P2-4)。
|
||||
// 滚动条:默认是主题给的 10px 深灰 thumb,压过组件本体(视觉 reviewer P2-4)。
|
||||
// 这里把它改成 **透明轨 + OutlineVariant thumb**;宽度 10px 是 `scrollable` 的内部常量
|
||||
// (该控件不暴露 Scrollbar 配置),所以只做了「低对比」这一半,已登记为偏离 D7。
|
||||
let body = scrollable(sections)
|
||||
|
||||
+24
-136
@@ -7,12 +7,17 @@
|
||||
//! `if hovered { 0.08 }`,第一周就会出现 0.08 / 0.1 / 0.12 三种 hover——
|
||||
//! 这一层存在的意义就是让「组件里出现 α 字面量」变成不可能:
|
||||
//!
|
||||
//! | 态 | α(令牌) |
|
||||
//! |---|---|
|
||||
//! | hover 0.08 / focus 0.10 / pressed 0.10 / dragged 0.16 | [`StateLayer`] |
|
||||
//! | disabled:容器 0.12 / 内容 0.38 | [`DISABLED_CONTAINER_ALPHA`] / [`DISABLED_CONTENT_ALPHA`] |
|
||||
//! | 态 | α | 令牌来源 |
|
||||
//! |---|---|---|
|
||||
//! | hover | 0.08 | [`StateLayer::Hover`] |
|
||||
//! | focus | 0.10 | [`StateLayer::Focus`] |
|
||||
//! | pressed | 0.10 | [`StateLayer::Pressed`] |
|
||||
//! | dragged | 0.16 | [`StateLayer::Dragged`](按钮家族不用,列表拖拽用) |
|
||||
//! | disabled 容器 | 0.12 | [`DISABLED_CONTAINER_ALPHA`](A1 是 0.10,差异登记 D8) |
|
||||
//! | disabled 内容 | 0.38 | [`DISABLED_CONTENT_ALPHA`] |
|
||||
//!
|
||||
//! 表里的数字**只能从令牌拿**,代码里不许出现(`components::gate` 会扫)。
|
||||
//! 上表右列是**唯一**允许的取值口;左列只是给人看的,代码里一个数字都不许出现
|
||||
//! (`components::gate` 会扫)。
|
||||
//!
|
||||
//! ## 两个消费形态
|
||||
//!
|
||||
@@ -26,16 +31,28 @@
|
||||
//! 逐像素一致,D27)。本模块只做「哪一态叠哪一层」的映射,不做任何颜色算术。
|
||||
|
||||
use iced::widget::button;
|
||||
use iced::{Color, Event};
|
||||
use iced::Color;
|
||||
|
||||
use unlucky_design::color::state::{DISABLED_CONTAINER_ALPHA, DISABLED_CONTENT_ALPHA};
|
||||
use unlucky_design::color::{Role, Scheme, StateLayer};
|
||||
|
||||
use crate::theme::{to_iced, Ui};
|
||||
|
||||
mod tracker;
|
||||
|
||||
// 本 mission 没有自绘控件,所以这条再导出在 crate 内暂时无人使用;
|
||||
// 它是给四条家族线(Slider / Switch 轨道要自绘)的公共入口,路径与拆分前一致。
|
||||
#[allow(unused_imports)]
|
||||
pub use tracker::Tracker;
|
||||
|
||||
// 本模块会被四条家族线各扩一次:加东西前先看行数余额(单文件 ≤400,AGENTS.md §3-1)。
|
||||
// `mod.rs` 只装「五态 + 颜色映射」这类纯函数;事件 → 态的**状态机在 [`tracker`]**,
|
||||
// 需要新的状态机就加到那边,别把本文件再撑到红线上。
|
||||
|
||||
/// 一个控件的视觉态(五态,M3 的完整集合)。
|
||||
///
|
||||
/// `Enabled` = 静息(不叠状态层);`Focused` / `Pressed` 的 α 相同(都 10%),所以图上一样是规范。
|
||||
/// `Enabled` 是**静息态**(不叠状态层);`Focused` / `Pressed` 在 M3 里 α 相同(都 10%),
|
||||
/// 所以两者在图上会一模一样——这是规范,不是漏画(12b §1.8 判据只要求 α 对得上)。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Interaction {
|
||||
/// 静息:可交互,不叠状态层。
|
||||
@@ -153,93 +170,6 @@ pub fn disabled_colors(ui: &Ui, surface: Role) -> (Color, Color) {
|
||||
|
||||
/// 自定义 `Widget` 的状态机:把 iced 事件折成 [`Interaction`]。
|
||||
///
|
||||
/// D39 §3 说的「状态机的公共部分」。自定义 `Widget` 的 `update` 里:
|
||||
/// `if tracker.event(event, cursor.is_over(bounds)) { shell.request_redraw(); }`,
|
||||
/// 画的时候读 `tracker.state()`。`event()` 只在**视觉态真的变了**时返回 `true`。
|
||||
// 本 mission 的按钮家族全部走 iced 的 `button`,没有自绘控件,所以 Tracker 目前只有单测在用;
|
||||
// 它在这里是给四个后续家族(Slider / Switch 轨道这类要自绘的)准备的公共状态机。
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Tracker {
|
||||
enabled: bool,
|
||||
hovered: bool,
|
||||
focused: bool,
|
||||
pressed: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Tracker {
|
||||
/// 新建;`enabled` 由组件自己决定(例如 `on_press.is_some()`)。
|
||||
pub fn new(enabled: bool) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// 启用/禁用热切换(禁用时清掉悬停与按下:禁用元素不该留着 hover 高亮)。
|
||||
pub fn set_enabled(&mut self, enabled: bool) -> bool {
|
||||
let changed = self.enabled != enabled;
|
||||
self.enabled = enabled;
|
||||
if !enabled {
|
||||
self.hovered = false;
|
||||
self.pressed = false;
|
||||
self.focused = false;
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
/// 键盘焦点(由 `operation`/`shell` 侧的聚焦消息驱动)。
|
||||
pub fn set_focused(&mut self, focused: bool) -> bool {
|
||||
let next = focused && self.enabled;
|
||||
let changed = self.focused != next;
|
||||
self.focused = next;
|
||||
changed
|
||||
}
|
||||
|
||||
/// 折进一个事件;返回「视觉态变了,要重绘吗」。
|
||||
///
|
||||
/// `over` = 指针是否在控件 bounds 内(`Cursor::is_over(bounds)` 的结果,由调用方算——
|
||||
/// 这样本函数不依赖 `Layout`,可以在单测里直接喂)。
|
||||
pub fn event(&mut self, event: &Event, over: bool) -> bool {
|
||||
if !self.enabled {
|
||||
return false;
|
||||
}
|
||||
|
||||
let before = self.state();
|
||||
match event {
|
||||
Event::Mouse(iced::mouse::Event::CursorMoved { .. }) => self.hovered = over,
|
||||
Event::Mouse(iced::mouse::Event::CursorLeft) => {
|
||||
self.hovered = false;
|
||||
self.pressed = false;
|
||||
}
|
||||
Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left)) => {
|
||||
self.pressed = over;
|
||||
}
|
||||
Event::Mouse(iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left)) => {
|
||||
self.pressed = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.state() != before
|
||||
}
|
||||
|
||||
/// 当前态(优先级:按下 > 焦点 > 悬停 > 静息;禁用最高)。
|
||||
pub const fn state(&self) -> Interaction {
|
||||
if !self.enabled {
|
||||
Interaction::Disabled
|
||||
} else if self.pressed {
|
||||
Interaction::Pressed
|
||||
} else if self.focused {
|
||||
Interaction::Focused
|
||||
} else if self.hovered {
|
||||
Interaction::Hovered
|
||||
} else {
|
||||
Interaction::Enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -355,46 +285,4 @@ mod tests {
|
||||
assert_eq!(container.a, 1.0, "禁用容器也是实色(CPU/GPU 一致)");
|
||||
assert_eq!(content.a, 1.0, "禁用内容也是实色");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracker_folds_events_into_the_five_states() {
|
||||
let mut tracker = Tracker::new(true);
|
||||
assert_eq!(tracker.state(), Interaction::Enabled);
|
||||
|
||||
let moved = Event::Mouse(iced::mouse::Event::CursorMoved {
|
||||
position: iced::Point::ORIGIN,
|
||||
});
|
||||
assert!(tracker.event(&moved, true), "首次悬停要报变化");
|
||||
assert_eq!(tracker.state(), Interaction::Hovered);
|
||||
assert!(!tracker.event(&moved, true), "重复悬停不该再报变化");
|
||||
|
||||
let press = Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left));
|
||||
assert!(tracker.event(&press, true));
|
||||
assert_eq!(tracker.state(), Interaction::Pressed);
|
||||
|
||||
let release = Event::Mouse(iced::mouse::Event::ButtonReleased(
|
||||
iced::mouse::Button::Left,
|
||||
));
|
||||
assert!(tracker.event(&release, true));
|
||||
assert_eq!(tracker.state(), Interaction::Hovered);
|
||||
|
||||
assert!(tracker.set_focused(true));
|
||||
assert_eq!(tracker.state(), Interaction::Focused, "焦点态优先于悬停");
|
||||
|
||||
let left = Event::Mouse(iced::mouse::Event::CursorLeft);
|
||||
tracker.event(&left, false);
|
||||
assert_eq!(tracker.state(), Interaction::Focused, "鼠标离开不影响焦点");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_tracker_swallows_everything() {
|
||||
let mut tracker = Tracker::new(true);
|
||||
assert!(tracker.set_enabled(false));
|
||||
assert_eq!(tracker.state(), Interaction::Disabled);
|
||||
|
||||
let press = Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left));
|
||||
assert!(!tracker.event(&press, true), "禁用态不响应事件");
|
||||
assert_eq!(tracker.state(), Interaction::Disabled);
|
||||
assert!(!tracker.set_focused(true), "禁用态拿不到焦点");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
//! 自定义 `Widget` 的状态机([`Tracker`]):把 iced 事件折成 [`Interaction`] 五态。
|
||||
//!
|
||||
//! 与 [`super`] 的分工:`super` 只有「态 → 颜色」的纯映射;**事件 → 态**在这里。
|
||||
//! 本 mission 的按钮家族全走 iced 的 `button`(自带四态),所以 `Tracker` 目前只有单测在用——
|
||||
//! 它给四个后续家族里**要自绘**的控件(Slider 轨道 / Switch 轨道 / 进度条)准备。
|
||||
//!
|
||||
//! 加东西前先看行数余额(单文件 ≤400,AGENTS.md §3-1)。
|
||||
|
||||
use iced::Event;
|
||||
|
||||
use super::Interaction;
|
||||
|
||||
/// 自定义 `Widget` 的状态机:把 iced 事件折成 [`Interaction`]。
|
||||
///
|
||||
/// 这是 D39 §3 说的「状态机的公共部分」。用法(自定义 Widget 的 `update` 里):
|
||||
///
|
||||
/// ```ignore
|
||||
/// if self.tracker.event(event, cursor.is_over(layout.bounds())) {
|
||||
/// shell.request_redraw(); // 只有真变了才重绘
|
||||
/// }
|
||||
/// let style = style_of(self.tracker.state());
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Tracker {
|
||||
enabled: bool,
|
||||
hovered: bool,
|
||||
focused: bool,
|
||||
pressed: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Tracker {
|
||||
/// 新建;`enabled` 由组件自己决定(例如 `on_press.is_some()`)。
|
||||
pub fn new(enabled: bool) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// 启用/禁用热切换(禁用时清掉悬停与按下:禁用元素不该留着 hover 高亮)。
|
||||
pub fn set_enabled(&mut self, enabled: bool) -> bool {
|
||||
let changed = self.enabled != enabled;
|
||||
self.enabled = enabled;
|
||||
if !enabled {
|
||||
self.hovered = false;
|
||||
self.pressed = false;
|
||||
self.focused = false;
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
/// 键盘焦点(由 `operation`/`shell` 侧的聚焦消息驱动)。
|
||||
pub fn set_focused(&mut self, focused: bool) -> bool {
|
||||
let next = focused && self.enabled;
|
||||
let changed = self.focused != next;
|
||||
self.focused = next;
|
||||
changed
|
||||
}
|
||||
|
||||
/// 折进一个事件;返回「视觉态变了,要重绘吗」。
|
||||
///
|
||||
/// `over` = 指针是否在控件 bounds 内(`Cursor::is_over(bounds)` 的结果,由调用方算——
|
||||
/// 这样本函数不依赖 `Layout`,可以在单测里直接喂)。
|
||||
pub fn event(&mut self, event: &Event, over: bool) -> bool {
|
||||
if !self.enabled {
|
||||
return false;
|
||||
}
|
||||
|
||||
let before = self.state();
|
||||
match event {
|
||||
Event::Mouse(iced::mouse::Event::CursorMoved { .. }) => self.hovered = over,
|
||||
Event::Mouse(iced::mouse::Event::CursorLeft) => {
|
||||
self.hovered = false;
|
||||
self.pressed = false;
|
||||
}
|
||||
Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left)) => {
|
||||
self.pressed = over;
|
||||
}
|
||||
Event::Mouse(iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left)) => {
|
||||
self.pressed = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.state() != before
|
||||
}
|
||||
|
||||
/// 当前态(优先级:按下 > 焦点 > 悬停 > 静息;禁用最高)。
|
||||
pub const fn state(&self) -> Interaction {
|
||||
if !self.enabled {
|
||||
Interaction::Disabled
|
||||
} else if self.pressed {
|
||||
Interaction::Pressed
|
||||
} else if self.focused {
|
||||
Interaction::Focused
|
||||
} else if self.hovered {
|
||||
Interaction::Hovered
|
||||
} else {
|
||||
Interaction::Enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Tracker` 的行为用例(自绘控件用,与颜色无关)。
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use iced::Event;
|
||||
|
||||
fn moved() -> Event {
|
||||
Event::Mouse(iced::mouse::Event::CursorMoved {
|
||||
position: iced::Point::ORIGIN,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracker_folds_events_into_the_five_states() {
|
||||
let mut tracker = Tracker::new(true);
|
||||
assert_eq!(tracker.state(), Interaction::Enabled);
|
||||
|
||||
assert!(tracker.event(&moved(), true), "首次悬停要报变化");
|
||||
assert_eq!(tracker.state(), Interaction::Hovered);
|
||||
assert!(!tracker.event(&moved(), true), "重复悬停不该再报变化");
|
||||
|
||||
let press = Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left));
|
||||
assert!(tracker.event(&press, true));
|
||||
assert_eq!(tracker.state(), Interaction::Pressed);
|
||||
|
||||
let release = Event::Mouse(iced::mouse::Event::ButtonReleased(
|
||||
iced::mouse::Button::Left,
|
||||
));
|
||||
assert!(tracker.event(&release, true));
|
||||
assert_eq!(tracker.state(), Interaction::Hovered);
|
||||
|
||||
assert!(tracker.set_focused(true));
|
||||
assert_eq!(tracker.state(), Interaction::Focused, "焦点态优先于悬停");
|
||||
|
||||
let left = Event::Mouse(iced::mouse::Event::CursorLeft);
|
||||
tracker.event(&left, false);
|
||||
assert_eq!(tracker.state(), Interaction::Focused, "鼠标离开不影响焦点");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_tracker_swallows_everything() {
|
||||
let mut tracker = Tracker::new(true);
|
||||
assert!(tracker.set_enabled(false));
|
||||
assert_eq!(tracker.state(), Interaction::Disabled);
|
||||
|
||||
let press = Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left));
|
||||
assert!(!tracker.event(&press, true), "禁用态不响应事件");
|
||||
assert_eq!(tracker.state(), Interaction::Disabled);
|
||||
assert!(!tracker.set_focused(true), "禁用态拿不到焦点");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user