docs: explain host widget interaction and visual states
This commit is contained in:
@@ -10,6 +10,7 @@ use std::hash::{Hash, Hasher};
|
||||
pub struct WidgetId(u64);
|
||||
impl WidgetId {
|
||||
/// 对调用方语义 key 求哈希,生成不随控件位置或标签变化的跨帧 WidgetId;相同 key 会得到同一身份。
|
||||
// widgets 的按钮按 Action 生成身份,拖动控件由 Painter 显式传语义 key;同帧重建几何不应重置身份。
|
||||
pub fn new(key: impl Hash) -> Self {
|
||||
// 语义 key 哈希为会话内身份;不依赖布局坐标,但调用方仍需避免重复 key,哈希并非全局唯一保证。
|
||||
let mut hash = std::collections::hash_map::DefaultHasher::new();
|
||||
@@ -37,6 +38,7 @@ pub enum Role {
|
||||
}
|
||||
impl Role {
|
||||
/// 判断控件角色是否可以接受键盘焦点。
|
||||
// Interaction::press 和 focus_next 共用资格规则,只有允许键盘导航的角色能通过这两个入口得到焦点。
|
||||
fn focusable(self) -> bool {
|
||||
// 行用于指针选择,分隔条/滚动条用于拖动,这三类不加入键盘 Tab 焦点循环。
|
||||
!matches!(self, Self::Splitter(_) | Self::Scrollbar(_) | Self::Row)
|
||||
@@ -56,10 +58,13 @@ pub enum Cursor {
|
||||
#[derive(Clone, Debug)]
|
||||
/// 本帧可命中的管理控件,绑定几何、角色、启用条件与可选 action。
|
||||
pub struct Target<A> {
|
||||
// 对应跨帧保留的焦点/捕获键,矩形可每帧变化;ID 本身不携带屏幕坐标。
|
||||
pub id: WidgetId,
|
||||
// 本帧逻辑命中矩形,通常与绘制一致,行/分隔条可显式用不同的绘制与交互范围。
|
||||
pub rect: Rect,
|
||||
pub role: Role,
|
||||
pub enabled: bool,
|
||||
// 激活时克隆交给宿主处理;分隔条/滚动条没有点击动作,拖动参数由上层布局另存。
|
||||
pub action: Option<A>,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -68,6 +73,7 @@ pub struct VisualState {
|
||||
pub hovered: bool,
|
||||
pub pressed: bool,
|
||||
pub focused: bool,
|
||||
// 由当前页面或工具数据决定的选中态,不从 hover/press 推断,禁用也不自动清除此数据状态。
|
||||
pub selected: bool,
|
||||
pub enabled: bool,
|
||||
}
|
||||
@@ -77,45 +83,55 @@ pub struct VisualState {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
/// 即时绘制之外保留的输入状态,确保按下与释放跨帧仍匹配同一控件。
|
||||
pub struct Interaction {
|
||||
// 最近逻辑指针位置;None 时 hit 不命中,即使仍保留 pointer_active 拖动捕获。
|
||||
pointer: Option<Point>,
|
||||
hovered: Option<WidgetId>,
|
||||
// 按下时捕获的控件;即使指针移动到别处,也保留原身份直到释放或取消。
|
||||
pointer_active: Option<WidgetId>,
|
||||
// 键盘按下时的焦点身份;释放时再次匹配,避免焦点已变却激活新控件。
|
||||
key_active: Option<WidgetId>,
|
||||
// 文本或按钮焦点身份与键盘来源标记分开;鼠标也能给可聚焦目标 focused,但不显示键盘焦点圈。
|
||||
focused: Option<WidgetId>,
|
||||
// 区分键盘导航和鼠标获得焦点,visual 仅为键盘来源显示焦点外观。
|
||||
keyboard_focus: bool,
|
||||
}
|
||||
impl Interaction {
|
||||
/// 按逆绘制顺序选择指针处的管理控件,避免重叠控件同时激活。
|
||||
// app、工具与其他交互方法共用,targets 追加顺序对应绘制层级,末尾目标优先。
|
||||
pub fn hit<'a, A>(&self, targets: &'a [Target<A>]) -> Option<&'a Target<A>> {
|
||||
// 无指针位置直接 None,pointer_left 后不会继续用上一次窗口坐标重新命中。
|
||||
let p = self.pointer?;
|
||||
// 几何命中包含禁用控件,因此上层禁用项会挡住下层项;是否允许操作由 press/cursor 分别判断。
|
||||
targets.iter().rev().find(|t| t.rect.contains(p))
|
||||
}
|
||||
/// 更新指针与悬停目标,但不在移动时直接触发点击。
|
||||
// 原生指针移动先更新真实 Interaction,Ui 在下一帧克隆它查询外观;移动本身不改捕获或焦点。
|
||||
pub fn pointer_moved<A>(&mut self, p: Point, targets: &[Target<A>]) {
|
||||
self.pointer = Some(p);
|
||||
self.hovered = self.hit(targets).map(|t| t.id);
|
||||
}
|
||||
/// 清除离开窗口后的指针/悬停状态。
|
||||
// app/DeveloperToolsState 的离开路径调用,仅清指针与 hover,外层决定是否进一步取消操作。
|
||||
pub fn pointer_left(&mut self) {
|
||||
// 离开窗口只清位置和悬停,保留捕获身份供外层拖动流程使用;完整失焦由 blur 处理。
|
||||
self.pointer = None;
|
||||
self.hovered = None;
|
||||
}
|
||||
/// 捕获启用控件的稳定身份,并按角色更新键盘焦点。
|
||||
// app 侧栏和 DeveloperToolsState 按下调用,克隆目标让上层按角色决定拖动或文本焦点,不跨调用借用 targets。
|
||||
pub fn press<A: Clone>(&mut self, targets: &[Target<A>]) -> Option<Target<A>> {
|
||||
// 鼠标按下撤销旧键盘激活并关闭键盘焦点外观;点空白或不可聚焦角色会清除 focused。
|
||||
self.key_active = None;
|
||||
self.keyboard_focus = false;
|
||||
// 先取最上层几何目标再按 enabled 过滤,不能因它被禁用而穿透到下层启用控件。
|
||||
let target = self.hit(targets).filter(|t| t.enabled).cloned();
|
||||
self.pointer_active = target.as_ref().map(|t| t.id);
|
||||
// 按角色更新焦点:空白、禁用或不可聚焦角色令焦点清空,捕获身份与焦点可不同。
|
||||
self.focused = target.as_ref().filter(|t| t.role.focusable()).map(|t| t.id);
|
||||
target
|
||||
}
|
||||
/// 只有释放时仍处于原启用目标内才返回 action,拖出后释放不触发。
|
||||
// 侧栏和工具鼠标抬起调用,必须匹配按下 ID;TextField 已在按下阶段处理定位,不再抬起激活。
|
||||
pub fn release<A: Clone>(&mut self, targets: &[Target<A>]) -> Option<A> {
|
||||
// take 先消费捕获,保证同一次按下最多激活一次;TextField 不走按钮 release action。
|
||||
let active = self.pointer_active.take()?;
|
||||
@@ -124,11 +140,13 @@ impl Interaction {
|
||||
.and_then(|t| t.action.clone())
|
||||
}
|
||||
/// 撤销当前操作的后续交付资格,并执行对应取消清理。
|
||||
// 侧栏折叠或焦点导航前调用,清未交付按压但保留指针、悬停和 focused;完整失焦另走 blur。
|
||||
pub fn cancel(&mut self) {
|
||||
self.pointer_active = None;
|
||||
self.key_active = None;
|
||||
}
|
||||
/// 撤销失焦后的键盘/指针激活状态,防止回到窗口时补发旧操作。
|
||||
// 窗口失焦或工具切走时调用,组合清捕获、焦点、键盘外观和指针位置,防止回来补发旧操作。
|
||||
pub fn blur(&mut self) {
|
||||
self.cancel();
|
||||
self.focused = None;
|
||||
@@ -136,7 +154,9 @@ impl Interaction {
|
||||
self.pointer_left();
|
||||
}
|
||||
/// 用本帧目标集合校正跨帧焦点和捕获,已消失控件不保留可激活身份。
|
||||
// 每帧 Ui 目标重建后由 sidebar::draw 或 developer_tools::build 调用,返回外观相关身份是否变化供上层决定重绘。
|
||||
pub fn reconcile<A>(&mut self, targets: &[Target<A>]) -> bool {
|
||||
// 保存更新前四种身份,末尾与更新后比较;键盘来源标记本函数不修改。
|
||||
let before = (
|
||||
self.hovered,
|
||||
self.pointer_active,
|
||||
@@ -158,24 +178,29 @@ impl Interaction {
|
||||
)
|
||||
}
|
||||
/// 返回当前仍持有焦点的稳定控件身份。
|
||||
// key_press/key_release 与工具键盘路由共用,只有当前集合仍有相同启用 ID 才返回目标。
|
||||
pub fn focused<'a, A>(&self, targets: &'a [Target<A>]) -> Option<&'a Target<A>> {
|
||||
targets
|
||||
.iter()
|
||||
.find(|t| Some(t.id) == self.focused && t.enabled)
|
||||
}
|
||||
/// 沿启用且可聚焦的目标循环导航,支持正向与反向遍历。
|
||||
// app/工具处理 Tab 导航时调用,返回目标克隆供外层映射文本焦点,顺序按本帧登记而非屏幕坐标排序。
|
||||
pub fn focus_next<A: Clone>(&mut self, targets: &[Target<A>], back: bool) -> Option<Target<A>> {
|
||||
// 键盘导航先取消未完成按压,避免切换焦点后补发旧激活;候选顺序沿本帧目标顺序。
|
||||
self.cancel();
|
||||
self.keyboard_focus = true;
|
||||
// 只收集 enabled 且 focusable 目标,禁用项和纯拖动区域不会占 Tab 一站。
|
||||
let candidates = targets
|
||||
.iter()
|
||||
.filter(|t| t.enabled && t.role.focusable())
|
||||
.collect::<Vec<_>>();
|
||||
// 空集合清焦点并返回,后面的 len-1 与取模依赖这里已确保非空。
|
||||
if candidates.is_empty() {
|
||||
self.focused = None;
|
||||
return None;
|
||||
}
|
||||
// 旧焦点可能已隐藏或禁用,找不到时按从首/末开始,而非沿用旧数组下标。
|
||||
let current = candidates.iter().position(|t| Some(t.id) == self.focused);
|
||||
// 有当前焦点时环绕前进/后退;无有效焦点时正向选首项、反向选末项。
|
||||
let index = match current {
|
||||
@@ -184,11 +209,13 @@ impl Interaction {
|
||||
None if back => candidates.len() - 1,
|
||||
None => 0,
|
||||
};
|
||||
// 把焦点写成语义身份而不是候选位置,下一帧重排后仍可校验同一控件。
|
||||
let target = candidates[index];
|
||||
self.focused = Some(target.id);
|
||||
Some(target.clone())
|
||||
}
|
||||
/// 为当前焦点控件记录键盘激活,后续释放仍须匹配目标。
|
||||
// Enter/Space 按下调用,记录当前启用非文本框焦点,重复按下是否抑制由上层事件路由决定。
|
||||
pub fn key_press<A>(&mut self, targets: &[Target<A>]) {
|
||||
self.key_active = self
|
||||
.focused(targets)
|
||||
@@ -196,6 +223,7 @@ impl Interaction {
|
||||
.map(|t| t.id);
|
||||
}
|
||||
/// 结束键盘激活并按有效控件身份返回操作。
|
||||
// Enter/Space 抬起调用,先消费旧捕获,再核对当前启用焦点与 ID,相同才返回 action。
|
||||
pub fn key_release<A: Clone>(&mut self, targets: &[Target<A>]) -> Option<A> {
|
||||
// 只有释放时仍聚焦相同启用目标才返回动作;take 防止重复 key-up 重复激活。
|
||||
let active = self.key_active.take()?;
|
||||
@@ -204,8 +232,10 @@ impl Interaction {
|
||||
.and_then(|t| t.action.clone())
|
||||
}
|
||||
/// 组合悬停、按下、焦点、选中和启用状态,供控件选择正确样式。
|
||||
// Ui::register 在绘制控件时查询,selected/enabled 由本帧数据传入,其余身份来自跨帧 Interaction。
|
||||
pub fn visual(&self, id: WidgetId, selected: bool, enabled: bool) -> VisualState {
|
||||
// 禁用控件不显示交互状态;指针按压需仍悬停才显示 pressed,拖出后保留捕获但取消按下外观。
|
||||
// hovered 只在启用时成立;键盘按下不要求鼠标悬停,鼠标按下外观则须仍停在原捕获目标。
|
||||
let hovered = enabled && self.hovered == Some(id);
|
||||
VisualState {
|
||||
hovered,
|
||||
@@ -217,10 +247,12 @@ impl Interaction {
|
||||
}
|
||||
}
|
||||
/// 返回本次拖动/按压捕获的控件,移动过程中不随悬停目标跳变。
|
||||
// widgets 的 splitter/scrollbar 用它保持拖出后的按压颜色;普通按钮不使用这个外观例外。
|
||||
pub fn captured(&self, id: WidgetId) -> bool {
|
||||
self.pointer_active == Some(id)
|
||||
}
|
||||
/// 按命中角色选择系统光标,例如文本、分隔条或可点击按钮。
|
||||
// DeveloperToolsState 把结果映射为 winit 原生光标,当前捕获的拖动角色优先于新悬停目标。
|
||||
pub fn cursor<A>(&self, targets: &[Target<A>]) -> Cursor {
|
||||
// 拖动分隔条/滚动条时优先沿用捕获目标的光标,避免指针跨过其他控件导致拖动光标跳变。
|
||||
let target = self
|
||||
@@ -231,6 +263,7 @@ impl Interaction {
|
||||
})
|
||||
})
|
||||
.or_else(|| self.hit(targets));
|
||||
// 命中禁用项返回禁止图标,无命中返回默认;其他角色按文本、调整、抓取或可点击分别映射。
|
||||
match target {
|
||||
Some(t) if !t.enabled => Cursor::Disabled,
|
||||
Some(t) => match t.role {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! 宿主管理控件的共享组件入口。主题、绘制原语和跨帧交互状态分离,用户 Endor 页面不依赖这里的控件树。
|
||||
//!
|
||||
//! 此模块不依赖 Endor AST、VM、UiNode 或 ui_bridge;只复用底层绘图、字体塑形和几何。
|
||||
//! sidebar 与 DeveloperTools 的 Painter 构造本帧 Ui,输入处理保留 Interaction,再用新 Target 集合校正跨帧捕获。
|
||||
// 跨帧保存 hover/press/focus,管理命中目标、键盘导航与控件视觉状态。
|
||||
mod interaction;
|
||||
// 提供管理界面统一的颜色、尺寸和排版常量,避免各工具各自定义主题。
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::display_list::Color;
|
||||
pub const BG: Color = Color::rgb(255, 255, 255);
|
||||
// A visibly separate chrome layer against the warm, near-white content canvas.
|
||||
pub const BAR: Color = Color::rgb(230, 233, 237);
|
||||
// 控件边框与未悬停分隔线共用,强调状态由 Theme.accent 覆盖而不是改写这个常量。
|
||||
pub const BORDER: Color = Color::rgb(226, 226, 226);
|
||||
pub const TEXT: Color = Color::rgb(35, 42, 53);
|
||||
pub const MUTED: Color = Color::rgb(103, 114, 130);
|
||||
@@ -22,10 +23,12 @@ pub const PALETTE: [Color; 6] = [
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
/// 当前管理界面的配色基础,派生状态颜色由方法统一计算。
|
||||
pub struct Theme {
|
||||
// 由 new 先做简单亮度限制,soft/hover/pressed 使用同一份调整后颜色生成状态背景。
|
||||
pub accent: Color,
|
||||
}
|
||||
impl Theme {
|
||||
/// 保存经过亮度限制的强调色,所有控件状态色都从该色与管理界面背景混合得到。
|
||||
// 每帧 sidebar/DeveloperTools 的 Ui 初始化调用,设置页输入也可显式使用 readable_accent。
|
||||
pub fn new(accent: Color) -> Self {
|
||||
// 构造时统一压低过亮强调色,之后 hover/pressed 等样式都从调整后的 accent 派生。
|
||||
Self {
|
||||
@@ -33,19 +36,23 @@ impl Theme {
|
||||
}
|
||||
}
|
||||
/// 以 10% 强调色混合背景,得到未悬停的柔和选中底色。
|
||||
// widgets::surface 处理选中态、Painter 画静态已选行时使用,背景固定为宿主 BG。
|
||||
pub fn soft(self) -> Color {
|
||||
mix(BG, self.accent, 0.10)
|
||||
}
|
||||
/// 以 17% 强调色混合背景,得到比 soft 更明显的悬停底色。
|
||||
// 控件 hover 底色与源码选区高亮共用,比 soft 更明显但仍是与白底混合的浅色。
|
||||
pub fn hover(self) -> Color {
|
||||
mix(BG, self.accent, 0.17)
|
||||
}
|
||||
/// 以 29% 强调色混合背景,增强按下状态;混色输出始终不透明。
|
||||
// widgets::surface 的按压优先分支使用,比例最高以区分普通悬停。
|
||||
pub fn pressed(self) -> Color {
|
||||
mix(BG, self.accent, 0.29)
|
||||
}
|
||||
}
|
||||
/// 按比例混合颜色通道,作为状态颜色的基础运算。
|
||||
// 状态背景、按压色块与悬停滚动手柄调用;只混 RGB,不按输入 alpha 做源覆盖合成。
|
||||
pub fn mix(a: Color, b: Color, amount: f32) -> Color {
|
||||
// 逐 RGB 通道线性插值并四舍五入,amount 钳到 [0,1];结果使用 rgb,输出 alpha 固定为 255。
|
||||
let channel =
|
||||
@@ -57,9 +64,11 @@ pub fn mix(a: Color, b: Color, amount: f32) -> Color {
|
||||
)
|
||||
}
|
||||
/// 根据背景选择可读的强调色,供选中与焦点状态使用。
|
||||
// Theme::new 与 Settings 的 Enter 应用路径调用,不依赖当前业务页面背景,也不执行通用主题持久化。
|
||||
pub fn readable_accent(mut c: Color) -> Color {
|
||||
// 用 299/587/114 加权亮度做简单阈值判断,过亮时每轮各通道乘 0.94 变暗。
|
||||
// 这是启发式规则,不是对任意背景执行通用对比度求解。
|
||||
// 整数通道每轮变小直至阈值以内;进入循环时 Color::rgb 会将输出 alpha 设为不透明,未进入则原样返回。
|
||||
while (c.red as u32 * 299 + c.green as u32 * 587 + c.blue as u32 * 114) / 1000 > 155 {
|
||||
c = Color::rgb(
|
||||
(c.red as f32 * 0.94) as u8,
|
||||
|
||||
@@ -14,13 +14,16 @@ use std::hash::Hash;
|
||||
|
||||
/// 一次按钮绘制的借用描述,包括显示、角色和启用/选中状态。
|
||||
pub struct Button<'a> {
|
||||
// 只在本次构建借用按钮文本,最终显示列表命令拥有自己的字符串,不依赖这个描述存活。
|
||||
pub label: &'a str,
|
||||
pub selected: bool,
|
||||
pub enabled: bool,
|
||||
// 默认 Button,可显式切 Toggle/Tab;角色影响焦点/光标和 Tab 底线,不替代 Action 身份。
|
||||
pub role: Role,
|
||||
}
|
||||
impl<'a> Button<'a> {
|
||||
/// 创建默认启用、未选中的普通按钮描述,借用 label 而不分配文本。
|
||||
// 侧栏与 Painter 用 builder 描述按钮,每帧重新构造,不在 Button 内保存 hover 或按下状态。
|
||||
pub fn new(label: &'a str) -> Self {
|
||||
Self {
|
||||
label,
|
||||
@@ -48,7 +51,9 @@ impl<'a> Button<'a> {
|
||||
|
||||
/// 本帧宿主控件绘制上下文,共同拥有显示列表与交互目标登记入口。
|
||||
pub struct Ui<A> {
|
||||
// 本帧逻辑空间命令,scene 或 developer_tools 外层统一按设备比例缩放。
|
||||
pub list: DisplayList,
|
||||
// 按追加顺序保存交互目标,Interaction::hit 逆序查找,追加时序也是覆盖命中优先级。
|
||||
pub targets: Vec<Target<A>>,
|
||||
pub theme: Theme,
|
||||
// 绘制使用跨帧交互状态的本轮副本,只查询 visual/captured;事件处理器在外部更新权威状态。
|
||||
@@ -56,6 +61,7 @@ pub struct Ui<A> {
|
||||
}
|
||||
impl<A: Hash> Ui<A> {
|
||||
/// 建立空显示列表和目标集合,保存本轮主题与交互状态快照。
|
||||
// sidebar::draw 与 developer_tools::build 创建本帧 Ui,传入上次事件更新后的 Interaction 克隆。
|
||||
pub fn new(theme: Theme, interaction: Interaction) -> Self {
|
||||
Self {
|
||||
list: DisplayList::default(),
|
||||
@@ -65,6 +71,7 @@ impl<A: Hash> Ui<A> {
|
||||
}
|
||||
}
|
||||
/// 追加无圆角填充矩形命令;不注册交互目标。
|
||||
// 背景、选中高亮、分隔线和手柄的通用原语,不改变任何跨帧状态。
|
||||
pub fn fill(&mut self, r: Rect, color: Color) {
|
||||
self.list.push(DrawCommand::FillRoundedRect {
|
||||
rect: r,
|
||||
@@ -73,9 +80,10 @@ impl<A: Hash> Ui<A> {
|
||||
});
|
||||
}
|
||||
/// 追加向内收缩的圆角描边,使传入矩形作为控件外边界。
|
||||
// surface、text_field 和 swatch 绘制焦点/边框时调用,下面对底层描边矩形留出内缩空间。
|
||||
fn outline(&mut self, r: Rect, color: Color, width: f32) {
|
||||
self.list.push(DrawCommand::StrokeRoundedRect {
|
||||
// 描边以路径中心为基准,向内缩半个线宽,使描边整体尽量位于传入矩形内;尺寸钳非负。
|
||||
// 传给底层的描边矩形四边各缩半线宽,额外保留内侧空间;小矩形宽高钳到非负。
|
||||
rect: Rect::new(
|
||||
r.x + width / 2.0,
|
||||
r.y + width / 2.0,
|
||||
@@ -88,6 +96,7 @@ impl<A: Hash> Ui<A> {
|
||||
});
|
||||
}
|
||||
/// 追加等宽或界面字体文字命令;调用方负责裁剪与可用宽度。
|
||||
// Painter 与侧栏调用,界面标签固定 12/18 逻辑字号/行高,code 则使用统一等宽样式。
|
||||
pub fn label(&mut self, x: f32, y: f32, value: &str, color: Color, code: bool) {
|
||||
self.list.push(DrawCommand::Text {
|
||||
origin: Point::new(x, y),
|
||||
@@ -105,6 +114,7 @@ impl<A: Hash> Ui<A> {
|
||||
});
|
||||
}
|
||||
/// 把控件身份、几何、角色、启用状态和 action 一起加入本帧交互目标。
|
||||
// 所有可交互控件共用登记入口;同一 id 的视觉响应与新矩形一起产生,但此处不执行命中测试或激活动作。
|
||||
fn register(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
@@ -126,6 +136,7 @@ impl<A: Hash> Ui<A> {
|
||||
response
|
||||
}
|
||||
/// 按视觉状态绘制控件背景和焦点轮廓;本方法不注册目标。
|
||||
// 按钮、文本框与行共用状态背景,base=None 时未交互/未选中可以完全不画底色。
|
||||
fn surface(&mut self, rect: Rect, s: VisualState, base: Option<Color>) {
|
||||
// 背景优先级为禁用、按下、悬停、选中、默认;焦点轮廓单独绘制,可与选中同时存在。
|
||||
let color = if !s.enabled {
|
||||
@@ -139,17 +150,21 @@ impl<A: Hash> Ui<A> {
|
||||
} else {
|
||||
base
|
||||
};
|
||||
// 仅有实际状态色或调用方 base 才追加填充,不凭空覆盖底层背景。
|
||||
if let Some(color) = color {
|
||||
self.fill(rect, color);
|
||||
}
|
||||
// 焦点圈独立叠在背景之上,键盘聚焦的已选按钮可同时有选中底色和强调轮廓。
|
||||
if s.focused {
|
||||
self.outline(rect, self.theme.accent, 1.5);
|
||||
}
|
||||
}
|
||||
/// 登记按钮目标,绘制状态背景、裁剪后的标签及可选 Tab 底线,返回组合视觉状态。
|
||||
// 侧栏与 Painter 的按钮/切换入口,返回 VisualState 可供调用方进一步画图标或其他状态装饰。
|
||||
pub fn button(&mut self, rect: Rect, action: A, button: Button<'_>) -> VisualState {
|
||||
// 默认以 action 哈希为稳定身份;同一帧多个独立控件若 action 完全相同会共享身份,调用方需合理区分。
|
||||
let id = WidgetId::new(&action);
|
||||
// Action 在此移入目标集合,视觉状态先取回本地用于生成背景、文字颜色及可选底线。
|
||||
let s = self.register(
|
||||
id,
|
||||
rect,
|
||||
@@ -188,10 +203,12 @@ impl<A: Hash> Ui<A> {
|
||||
self.theme.accent,
|
||||
);
|
||||
}
|
||||
// 标签和 Tab 底线绘制结束后恢复外层裁剪,返回的状态仅为本轮快照。
|
||||
self.list.push(DrawCommand::PopClip);
|
||||
s
|
||||
}
|
||||
/// 登记文本输入角色并绘制背景和焦点/悬停描边,不负责实际文本与光标。
|
||||
// Network Filter 和 Settings 自定义颜色框使用,仅负责框的目标/外观,实际文字与编辑缓冲由工具状态维护。
|
||||
pub fn text_field(&mut self, rect: Rect, action: A, focused: bool) {
|
||||
let s = self.register(
|
||||
WidgetId::new(&action),
|
||||
@@ -218,6 +235,7 @@ impl<A: Hash> Ui<A> {
|
||||
/// A terminal prompt shares its surface with output; its caret is the focus cue.
|
||||
/// 只登记无边框终端输入的交互区域,外观由调用方绘制。
|
||||
// 终端输入与输出共享背景,只注册文本输入命中区,不绘制框线或文字,光标由 Console 负责。
|
||||
// Console 使用无框输入命中区域,焦点由按下的 Focus 动作建立,光标与内容由 console::build 自行绘制。
|
||||
pub fn inline_input(&mut self, rect: Rect, action: A) {
|
||||
self.register(
|
||||
WidgetId::new(&action),
|
||||
@@ -230,6 +248,7 @@ impl<A: Hash> Ui<A> {
|
||||
}
|
||||
/// 分别接受行背景区域和可见命中区域,登记行选择并绘制其状态背景。
|
||||
// 行背景可绘制完整行,但 hit 可裁到可见部分;不要把裁剪区外的行注册成可点击区域。
|
||||
// Painter 为请求、AST 等有动作的显示行调用,paint 可是完整行,hit 只允许可见片段。
|
||||
pub fn row(&mut self, paint: Rect, hit: Rect, action: A, selected: bool) {
|
||||
let s = self.register(
|
||||
WidgetId::new(&action),
|
||||
@@ -242,6 +261,7 @@ impl<A: Hash> Ui<A> {
|
||||
self.surface(paint, s, None);
|
||||
}
|
||||
/// 登记色块目标,绘制按压变暗、外圈及选中勾号。
|
||||
// Settings 调色板调用,颜色来自候选色,selected 来自当前强调色比较;点击只登记主题 Action。
|
||||
pub fn swatch(&mut self, rect: Rect, action: A, color: Color, selected: bool) {
|
||||
let s = self.register(
|
||||
WidgetId::new(&action),
|
||||
@@ -251,7 +271,7 @@ impl<A: Hash> Ui<A> {
|
||||
selected,
|
||||
true,
|
||||
);
|
||||
// 滚动条手柄捕获时保持强调色,悬停使用混色,其余用中性底色;实际偏移换算由调用方处理。
|
||||
// 色块保留传入颜色,按下时向正文深色混合 20% 变暗;外圈和选中勾号在下方单独叠加。
|
||||
self.fill(
|
||||
rect,
|
||||
if s.pressed {
|
||||
@@ -282,6 +302,7 @@ impl<A: Hash> Ui<A> {
|
||||
}
|
||||
}
|
||||
/// 登记扩大的拖动命中区,绘制捕获/悬停强调线。
|
||||
// DeveloperTools 父入口最后统一调用,显式 id 来自分隔类别,axis 表示拖动方向而非线条延伸方向。
|
||||
pub fn splitter(&mut self, rect: Rect, id: WidgetId, axis: Axis) {
|
||||
// 分隔线命中区四边扩展 3,提高细线可拖动性;真实绘制线宽与命中区域独立。
|
||||
let hit = Rect::new(
|
||||
@@ -290,6 +311,7 @@ impl<A: Hash> Ui<A> {
|
||||
rect.width + 6.0,
|
||||
rect.height + 6.0,
|
||||
);
|
||||
// 无点击 action 但保留拖动角色与命中,外层 press 再用布局记录决定调整哪块区域。
|
||||
let mut s = self.register(id, hit, Role::Splitter(axis), None, false, true);
|
||||
// 分隔条捕获后即使拖出命中区仍显示按压态,不采用普通按钮“拖出取消外观”的规则。
|
||||
s.pressed |= self.interaction.captured(id);
|
||||
@@ -298,6 +320,7 @@ impl<A: Hash> Ui<A> {
|
||||
} else {
|
||||
theme::BORDER
|
||||
};
|
||||
// 悬停/捕获时沿操作轴加粗到 3 单位:Horizontal 画竖线,Vertical 画横线。
|
||||
let line = if s.hovered || s.pressed {
|
||||
match axis {
|
||||
Axis::Horizontal => Rect::new(rect.x - 1.0, rect.y, 3.0, rect.height),
|
||||
@@ -309,8 +332,10 @@ impl<A: Hash> Ui<A> {
|
||||
self.fill(line, color);
|
||||
}
|
||||
/// 登记滚动手柄目标并绘制其状态颜色,偏移和拖动倍率由外部维护。
|
||||
// Painter 在内容裁剪弹出后调用,只有手柄几何和轴进入 Ui,内容偏移及倍率仍由外层状态管理。
|
||||
pub fn scrollbar(&mut self, rect: Rect, id: WidgetId, axis: Axis) {
|
||||
let mut s = self.register(id, rect, Role::Scrollbar(axis), None, false, true);
|
||||
// 拖动捕获时即使指针移到手柄外仍保持强调色,不能套用普通按钮拖出后取消按下外观的规则。
|
||||
s.pressed |= self.interaction.captured(id);
|
||||
self.fill(
|
||||
rect,
|
||||
|
||||
Reference in New Issue
Block a user