gpui-base Sheet 原语详解:从边缘进入的模态表面、焦点陷阱与关闭流程

gpui-base Sheet 原语详解:从边缘进入的模态表面、焦点陷阱与关闭流程 gpui-base Sheet 原语详解从边缘进入的模态表面、焦点陷阱与关闭流程【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kitSheet是gpui-baseGPUI 生态基础行为层见 crates/base/Cargo.toml提供的一种模态原语它从窗口某个边缘滑入典型如右侧设置面板同时接管关闭确认与焦点管理。本文以官方文档 website/base/primitives/sheet.md 为主线结合 Sheet 核心实现 与可运行 showcase 源码讲清它的构造 API、状态与事件模型、底层渲染结构、遮罩交互策略及测试契约帮助你直接在自己的 GPUI 应用中原样落地。Sheet 与gpui-base中所有原语一样只提供行为与语义结构不施加任何产品视觉语言——布局、定位、颜色、尺寸与动效全部交由调用方通过 GPUI 标准样式 trait 表达。读完本文你将能够运行官方示例、按受控状态驱动 Sheet 开关、利用overlay/surface组合出自有设计系统的侧滑面板并理解 Escape、遮罩点击与焦点陷阱的完整行为闭环。快速运行官方示例文档引用的可运行入口是 crates/base/examples/native/src/bin/components.rs它读取命令行第一个参数作为组件名并编译共享的 showcase 实现crates/base/examples/showcase/mod.rscargo run -p gpui-base-examples -- sheetgpui-base-examples是gpui-basecrate 下的示例二进制包名示例目录位于 crates/base/examples发布时被排除在正式包外见 crates/base/Cargo.toml#L6-L8。不带参数时默认显示组件总览页overview。同一个 showcase 还会被编译为 WASM 预览由 showcase/mod.rs 中的run_embedded与run_native两个入口分别驱动因此原生与浏览器预览渲染的是同一份源码。运行后你会看到页面中央一个 Open settings 按钮点击后从右侧滑出设置面板覆盖半透明遮罩点击遮罩或按 Escape 可关闭。导入方式在应用代码中推荐通过gpui-kitfacade 导入facade 的层级映射见 crates/kit/src/lib.rsuse gpui_kit::base::{Sheet};其中gpui_kit::base对应gpui-basecratebase层总是可用无需 feature。当然你也可以直接use gpui_base::Sheet;它由 crates/base/src/lib.rs 公开导出模块声明在 crates/base/src/lib.rs#L56。构造与公开 APISheet是一个实现了IntoElement的宿主结构核心定义见 crates/base/src/sheet.rs#L31-L43。构造入口为Sheet::new(cx)随后通过 Builder 风格方法组装内容与行为方法签名要点默认值 / 说明new(cx)fn new(cx: mut App) - Self创建宿主内部生成FocusHandle并绑定到Sheet键盘上下文overlay(...)impl IntoElement遮罩元素未设置则不渲染遮罩层surface(...)impl IntoElement表面面板主体元素未设置则无内容overlay_closable(bool)布尔默认true决定点击遮罩左键是否触发关闭流程on_close(handler)Fn(ClickEvent, mut Window, mut App)关闭通知回调在request_close之后执行request_close(...)Fn(mut Window, mut App)#[doc(hidden)]关闭请求回调先于on_close执行focus_handle(FocusHandle)覆盖内部焦点句柄#[doc(hidden)]主要用于测试注入dismiss_before_y(Pixels)纵向裁剪阈值#[doc(hidden)]遮罩点击在阈值线以上时被忽略用于「吸顶区域不可关闭」场景此外Sheet实现了Styledcrates/base/src/sheet.rs#L109-L113宿主本身的样式可通过.style()/GPUI 样式链式方法细化。默认值来自构造器crates/base/src/sheet.rs#L45-L59overlay_interactive true、overlay_closable truerequest_close与on_close初始为空实现。状态与事件受控打开与关闭顺序文档明确Sheet 的打开与关闭「镜像 Dialog而 placement进入边缘由你决定」。推荐做法是把受控状态放在父级渲染类型或 GPUI Entity 中在回调里更新状态并调用cx.notify()不要在每次渲染时重建持久 Entity。关闭流程的调用顺序由内部close辅助函数保证crates/base/src/sheet.rs#L17-L21request_close(window, cx) → on_close(ClickEvent::default(), window, cx)即先请求关闭再通知关闭。Escape 键走同一条路径Sheet::init在应用初始化时注册KeyBinding::new(escape, Cancel, Some(CONTEXT))crates/base/src/sheet.rs#L23-L25渲染时宿主设置key_context(Sheet)并挂接Cancel动作crates/base/src/sheet.rs#L132-L138。完整 Rust 示例showcase 源码以下即文档内嵌、由可运行 showcase 直接使用的完整实现出自 crates/base/examples/showcase/components/sheet.rs。它演示了受控布尔状态、request_closeon_close双回调、遮罩与表面元素的自定义以及状态更新后的cx.notify()use gpui::relative; use super::*; impl BaseShowcase { pub(in super::super) fn sheet(self, cx: mut ContextSelf) - impl IntoElement { let open self.sheet_open; let entity cx.entity().downgrade(); let open_sheet entity.clone(); let trigger Button::new(open-sheet) .h_7() .px_2() .text_xs() .flex() .items_center() .justify_center() .border_1() .border_color(super::example_rgb(0x171717)) .bg(super::example_rgb(0xffffff)) .child(Open settings) .on_click(move |_, _, cx| { _ open_sheet.update(cx, |this, cx| { this.sheet_open true; cx.notify(); }); }); div() .size_full() .min_h_64() .text_xs() .flex() .items_center() .justify_center() .child(trigger) .when(open, |this| { this.child( Sheet::new(cx) .request_close({ let entity entity.clone(); move |_, cx| { _ entity.update(cx, |this, cx| { this.sheet_open false; cx.notify(); }); } }) .overlay( div() .absolute() .inset_0() .bg(super::example_rgb(0x000000)) .opacity(0.15), ) .surface( div() .absolute() .right_0() .top_0() .h_full() .w(px(210.)) .p_3() .bg(super::example_rgb(0xffffff)) .border_1() .border_color(super::example_rgb(0x171717)) .child( div() .font_weight(gpui::FontWeight::SEMIBOLD) .child(Settings), ) .child( div().mt_4().child(Workspace name).child( div() .mt_1() .h_7() .px_2() .flex() .items_center() .border_1() .border_color(super::example_rgb(0xa3a3a3)) .child(Acme Studio), ), ) .child( div() .mt_2() .text_color(super::example_rgb(0x525252)) .child(Update the workspace preferences for your team.), ) .child( div() .mt_4() .py_1() .border_t_1() .border_color(super::example_rgb(0xd4d4d4)) .child(Notifications · Enabled), ) .child( div().mt_3().flex().justify_end().child( Button::new(close-sheet) .h_7() .line_height(relative(1.)) .px_3() .flex() .items_center() .justify_center() .bg(gpui::black()) .text_color(gpui::white()) .child(Done) .on_click({ let entity entity.clone(); move |_, _, cx| { _ entity.update(cx, |this, cx| { this.sheet_open false; cx.notify(); }); } }), ), ), ), ) }) } }要点拆解受控开关self.sheet_open是 showcase 上持久化的布尔字段定义于 crates/base/examples/showcase/mod.rs#L138触发按钮与request_close都只更新该字段并cx.notify()。关闭的双入口一致性遮罩点击 / Escape / 面板内 Done 按钮都会将sheet_open置回false——前两者走request_close按钮直接写状态殊途同归。placement 由 surface 决定示例用absolute().right_0().top_0().h_full().w(px(210.))把表面钉在右侧改成bottom_0/left_0/居中即可实现下边缘、左边缘或居中弹层。元素 ID 稳定性Button::new(open-sheet)、Button::new(close-sheet)使用稳定字符串 ID便于测试与无障碍。渲染原理锚定全屏宿主、焦点陷阱与键盘上下文RenderOnce实现crates/base/src/sheet.rs#L115-L167是理解 Sheet 行为的关键全视口锚定宿主被包在anchored().position(point(px(0.), px(0.)))中尺寸取window.viewport_size()并设置absolute().top_0().left_0()、w(viewport.width).h(viewport.height)——因此无论表面钉在哪条边遮罩总能覆盖整个视口。稳定宿主 IDid(sheet-host)并调用test_support()为 GPUI 测试基础设施提供支持。焦点管理track_focus(self.focus)focus_trap(sheet, self.focus)注册焦点陷阱打开时焦点被捕获在 Sheet 内关闭后恢复——这正是文档无障碍部分「trap and restore focus」的底层实现。键盘上下文key_context(CONTEXT)Sheet使 Escape 的Cancel绑定仅在 Sheet 处于激活上下文时生效on_action里先cx.propagate()再走统一的close流程。遮罩交互可关闭性、可交互性与纵向裁剪线遮罩行为集中在 crates/base/src/sheet.rs#L139-L162overlay_interactive默认true控制是否挂接on_any_mouse_down监听false时遮罩完全不拦截事件。任何鼠标按下都会cx.stop_propagation()防止穿透到下层内容。仅当overlay_closable event.button MouseButton::Left时触发关闭流程。dismiss_before_y裁剪线若点击位置y top在裁剪线上方则忽略该次点击——典型用途是「顶部工具栏区域点击不关闭面板」。测试与行为契约Sheet 的行为契约有完整单测背书全部位于 crates/base/src/sheet.rs#L169-L269测试验证内容overlay_close_requests_then_notifies左键点击遮罩后事件序列严格为[request, closed]non_closable_overlay_does_not_request_closeoverlay_closable(false)时点击遮罩不产生任何事件escape_uses_the_same_close_order_and_registers_focus_trapEscape 走相同关闭顺序且焦点陷阱已注册active_focus_trap非空pointer_above_the_dismiss_cutoff_is_ignored裁剪线上方点击被忽略、下方点击正常关闭测试通过gpui::TestAppContextVisualTestContext模拟点击与键盘动作使用request_close/on_close记录事件名并断言顺序是复现行为的最佳参考。无障碍文档给出的无障碍要求与 Dialog 一致为 Sheet 提供标题、捕获并恢复焦点、提供明确的关闭途径。落到实现上焦点陷阱由focus_trap(sheet, self.focus)保证与 focus_trap.rs 中的FocusTrapElement协作导出见 crates/base/src/lib.rs#L103-L105。Escape 关闭由Sheet键盘上下文承载。表面元素本身是无样式 div屏幕阅读器语义如 role、aria-label由调用方在surface/overlay内容中补充。使用注意事项元素 ID 稳定在overlay、surface及内部交互元素上使用稳定 ID如Button::new(close-sheet)便于定位、测试与无障碍引用。状态管理受控状态放在父级 Entity 或渲染类型上回调中cx.notify()切勿在每次 render 中重建持久 Entity。不要复用文档内嵌的视觉样式示例中的黑白配色、210px宽度仅用于演示应在消费设计系统中验证 focus、hover、active、selected、disabled、reduced-motion减弱动效与 high-contrast高对比度下的外观。与 Dialog 的取舍Sheet 适合「边缘抽屉 上下文设置」类场景居中弹窗需求请参考 dialog 文档实现见 crates/base/src/dialog.rs。若使用完整设计语言gpui-component层提供带样式的 Sheet 组件见 crates/component/src/sheet.rs适合直接落地到成品界面gpui-base原语则保留最大自由度。延伸阅读本文档原文website/base/primitives/sheet.md核心实现与单测crates/base/src/sheet.rsshowcase 共享实现含BaseShowcase与组件分派crates/base/examples/showcase/mod.rs原生入口组件名作为 CLI 参数crates/base/examples/native/src/bin/components.rsgpui-kitfacade 与层级说明crates/kit/src/lib.rsgpui-base包描述与特性test-support、inspectorcrates/base/Cargo.toml【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考