资讯详情

gpui-kit Collapsible 原语:从受控状态到运动过渡的完整实现解析

📅 2026/9/15 13:41:36 | 华诺云谱 👁 阅读
gpui-kit Collapsible 原语:从受控状态到运动过渡的完整实现解析
gpui-kit Collapsible 原语从受控状态到运动过渡的完整实现解析【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kitCollapsible折叠/展开区域是 GPUI Base 层提供的一个不规定触发器样式的可组合显隐原语。本指南以 Collapsible 文档 为主线结合其权威示例与底层源码crates/base/src/collapsible.rs、crates/base/src/motion/reveal.rs完整讲解其导入方式、受控状态管理、API 用法、Motion 过渡集成与可访问性要求让你能基于它搭建符合自身设计系统的可折叠交互区域。设计定位只提供行为与语义结构不规定视觉和所有 GPUI Base 原语一样Collapsible只提供行为behavior和语义结构semantic structure不规定产品的视觉语言。它不会替你决定触发器长什么样、展开动画用哪种缓动、内容区用什么间距——这些全部交给 GPUI 样式Styled和导出的部件如Button、div按你的设计系统组合实现。这一原则在源码中得到直接印证crates/base/src/collapsible.rs中的Collapsible结构体仅由四部分组成pub struct Collapsible { base: Div, children: VecChild, open: bool, reveal: Option(ElementId, f32), }其中base: Div只是用于承载样式的容器open是唯一的行为状态reveal则是可选的 Motion 过渡钩子后文详述没有任何内建的主题色、边框或字体设置。补充说明仓库中同时存在gpui-component门面层facade版本的 Collapsiblecrates/component/src/collapsible.rs它在 Base 原语之上增加了v_flex布局与可选的运动过渡但本文聚焦 Base 层的无样式原语两者 API 结构一致、可以对照阅读。快速上手运行示例与导入运行原生示例示例位于crates/base/examples/showcase/components/collapsible.rs原生示例与网页上方的 WASM 预览共用同一份实现。启动命令cargo run -p gpui-base-examples -- collapsible导入方式use gpui_kit::base::{Collapsible};gpui-kit统一重导出了 Base 层的全部类型见 crates/kit/src/lib.rs 的pub use ::gpui_base as base;而Collapsible在 Base 库中的导出位于 crates/base/src/lib.rspub use collapsible::Collapsible;结构与 APICollapsible 的公开 API 极小只有三个构造/组合方法其余表现能力全部来自 GPUI 标准 trait方法签名作用newpub fn new() - Self创建一个初始为关闭状态的 Collapsibleopenpub fn open(mut self, open: bool) - Self设置受控的展开/收起状态contentpub fn content(mut self, content: impl IntoElement) - Self设置可折叠的内容区域Styled通过base.style()把样式委托给内部Divimpl Styled for Collapsible因此可以直接链式调用.w_64()、.mt_2()等一切 GPUI 样式方法。ParentElement普通子元素如触发器通过children收集为Child::Element与内容区一起按添加顺序渲染。渲染逻辑impl RenderOnce for Collapsible是理解行为的关键Child::Content(content) match self.reveal { Some((id, progress)) { Some(MotionReveal::new(id.clone(), *progress, content).into_any_element()) } None self.open.then_some(content), },即未配置reveal时关闭状态下内容直接不渲染unmount配置了reveal时内容始终保持挂载由MotionReveal按进度裁切显隐。这一分支行为已被 Base 层测试验证见 crates/base/src/collapsible.rs 的content_is_only_rendered_while_openopen false时debug_bounds(content)为Noneopen true时存在。完整示例受控状态 自定义触发器文档引用的权威实现位于 crates/base/examples/showcase/components/collapsible.rs原生与浏览器预览编译的是同一文件。下面是其核心逻辑impl BaseShowcase { pub fn collapsible(self, cx: mut ContextSelf) - impl IntoElement { let open self.collapsible_open; let entity cx.entity().downgrade(); Collapsible::new() .open(open) .w_64() .child( div() .flex() .items_center() .justify_between() .child(div().text_xs().child(gpui/base · 3 repositories)) .child( Button::new(collapsible-trigger) .size_7() .border_1() .border_color(super::example_rgb(0xd4d4d4)) .flex() .items_center() .justify_center() .on_click(move |_, _, cx| { _ entity.update(cx, |this, cx| { this.collapsible_open !this.collapsible_open; cx.notify(); }); }) .child(if open { − } else { }), ), ) .content( div() .mt_2() .flex() .flex_col() .gap_2() .children(/* 折叠区域内的列表项 */), ) } }这个示例演示了三条关键经验触发器样式完全由你决定这里用的是Button::new(collapsible-trigger)并自行切换−/符号换成任意元素纯div、图标、文本按钮都不影响 Collapsible 的行为。打开状态由父级持有self.collapsible_open是展示页BaseShowcase的字段初始值为false见 crates/base/examples/showcase/mod.rs父级渲染函数每次读取它来驱动.open(...)。回调更新持久 entity 而非临时状态点击时通过降级的 entity 句柄entity.update(cx, ...)修改字段并调用cx.notify()触发重渲染不要在每次渲染时重建持久 entity。状态与事件父级持有状态回调中更新受控状态放在哪里受控的打开状态应保存在父渲染类型如示例中的BaseShowcase或 GPUI entity 中而不是放在 Collapsible 实例内部——因为 Collapsible 本身是纯函数式的每个方法都返回Self不维护任何内部可变状态。推荐的状态更新范式.on_click(move |_, _, cx| { _ entity.update(cx, |this, cx| { this.collapsible_open !this.collapsible_open; cx.notify(); // 通知 gpui 需要重绘 }); })要点通过cx.entity().downgrade()拿到弱引用后再更新避免闭包持有强引用造成环cx.notify()必不可少否则界面不会响应状态变化持久 entity 只在创建时构建一次不要在render里反复cx.new(...)。状态与 Motion 的配合文档指出“内容可结合 Motion 做进入与退出过渡”。在 Base 层这个钩子就是Collapsible::reveal(id, progress)传入一个稳定的ElementId和归一化的进度值0.0 关闭、1.0 完全展开Collapsible 内部会构造MotionReveal见 crates/base/src/motion/reveal.rs。MotionReveal的实现思路从源码结构看是在request_layout阶段把宽度设为relative(1.0)占满父容器高度按height * progress缩放其中height是缓存在元素状态RevealState中的内容测量高度在prepaint阶段用layout_as_root以MinContent高度测量子内容测量值变化时调用window.request_animation_frame()请求下一帧绘制时用window.with_content_mask(ContentMask { bounds })裁剪内容让被缩放区域内的子元素保持完整渲染而不会溢出。因此使用 Motion 展开/收起时你只需要在状态更新循环中驱动progress例如用crates/base/src/motion.rs导出的spring/transition函数把bool转成 0→1 的插值Collapsible 会保持内容挂载并平滑揭示。MotionReveal与spring等全部运动原语均已从 Base 层公开导出见 crates/base/src/lib.rs。若不需要动画就不要调用reveal——此时关闭状态下内容会被直接卸载这通常是最省资源也最简单的交互形态。可访问性Collapsible 只负责行为可访问性契约需要触发器元素来兑现展开状态触发器应把当前open状态暴露给辅助技术在 GPUI 中可通过触发器的语义属性或自定义元素实现例如用aria-expanded等价物标记。控制关系触发器应明确表达“它控制着哪个内容区域”的关联关系确保屏幕阅读器能够把按钮与其控制的折叠面板对应起来。注意事项在消费端验证状态样式文档特别强调由于 Base 原语不带视觉请在支持的位置使用稳定元素 ID并在消费端设计系统中验证以下状态焦点focus与悬停hover按下pressed与选中selected禁用disabled减少动态效果reduced motion下的表现高对比度high contrast下的表现稳定元素 ID 尤其重要如果你使用reveal做过渡MotionReveal依赖ElementId在帧间保持状态request_layout中window.with_element_state按global_id存取缓存高度ID 不稳定会导致测量缓存丢失、动画闪跳。与其他实现的对照仓库的gpui-component门面层在 Base 原语之上提供了带样式的进阶版crates/component/src/collapsible.rs它内部直接包裹gpui_base::Collapsible并新增motion_id(id)传入稳定 ID 后用主题的spring_controlcx.theme().motion_tokens().spring_control驱动spring把open转为 0→1 的进度再调用base.reveal(id, progress)实现可逆的测量揭示动画见其测试motion_id_keeps_closed_content_mounted_for_reversible_reveal输出前追加v_flex()保证触发器和内容纵向排列。如果你只需要最纯粹的行为原语直接用gpui_kit::base::Collapsible如果需要开箱即用的垂直布局与主题化过渡可对照阅读门面层实现作为参考。小结Collapsible 是Base 层最小化、无样式的显隐原语open控制行为Styled/ParentElement控制表现与结构状态永远放在父级回调中修改并cx.notify()不要在渲染时重建 entity需要动画时使用reveal(id, progress)结合MotionReveal内容保持挂载并平滑揭示不需要动画时关闭即卸载可访问性与状态样式验证属于消费端职责触发器需暴露展开状态与控制关系并在焦点、悬停、按下、选中、禁用、减少动效与高对比度场景下逐一确认。【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。