Zed GPUI 示例实战:从 Hello World 到虚拟列表与测试框架的 GPU 加速 UI 开发指南
Zed GPUI 示例实战从 Hello World 到虚拟列表与测试框架的 GPU 加速 UI 开发指南【免费下载链接】zedCode at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.项目地址: https://gitcode.com/GitHub_Trending/ze/zedGPUI 是 Zed 编辑器自研的 GPU 加速 UI 框架当前仓库中 gpui crate 版本为 0.2.2描述为 Zeds GPU-accelerated UI framework。crates/gpui/examples/目录下的示例集合是学习 GPUI 最快、最可靠的途径每一个示例都是一个可直接运行的最小应用覆盖窗口创建、布局、输入、图片渲染、动画、系统通知与测试基础设施等主题。本文以 GPUI 示例 README 为主线逐一解析各示例的用途并结合 hello_world.rs、uniform_list.rs、grid_layout.rs、testing.rs 等源码讲解示例背后的通用骨架、核心 API 与测试模式读完后你可以独立编写并测试一个小型 GPUI 应用。一、运行方式与示例总览所有示例都从 Zed 仓库根目录运行通用命令格式为cargo run -p gpui --example hello_world其中hello_world替换为 Cargo.toml 中[[example]]声明的任意示例名。例如测试示例的运行命令为cargo run -p gpui --example testing而它的测试则要用带 feature 的命令执行cargo test -p gpui --example testing --features test-support在 crates/gpui/Cargo.toml 中可以看到[[example]]条目显式声明了每个示例的名称与入口路径多数直接指向examples/下的.rs文件部分示例入口位于子目录中例如image→examples/image/image.rssvg→examples/svg/svg.rsview_example→examples/view_example/view_example_main.rs按 README 的分类示例可归纳为五组分组示例主题入门hello_world、input、uniform_list、testing应用骨架、文本输入、虚拟列表、测试布局与样式grid_layout、opacity、pattern、shadow、text、text_layout、text_wrapper网格布局、透明度、图案背景、阴影、文本渲染交互anchor、data_table、drag_drop、focus_visible、mouse_pressure、popover、scrollable、tab_stop锚点定位、表格、拖放、焦点样式、悬浮层、滚动图片/绘制/动画animation、gif_viewer、gradient、image、image_gallery、image_loading、painting、svg动画、GIF、渐变、图片加载、Canvas 绘制、SVG窗口与应用行为move_entity_between_windows、on_window_close_quit、set_menus、system_notifications、window、window_positioning、window_shadow实体迁移、退出行为、菜单、通知、多类窗口专项active_state_bug、layer_shell、list_example、ownership_post、paths_bench、tree面向 GPUI 自身开发者的复现、基准与文档示例所有示例共享的通用骨架阅读任意一个示例源码如 hello_world.rs都会看到同一段启动契约fn run_example() { application().run(|cx: mut App| { if !example_support::load_fonts(cx) { return; } let bounds Bounds::centered(None, size(px(500.), px(500.0)), cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, |_, cx| { cx.new(|_| HelloWorld { text: World.into() }) }, ) .unwrap(); cx.activate(true); }); }这个骨架包含四个关键动作gpui_platform::application().run(...)创建并启动一个App应用上下文闭包参数cx: mut App是全局上下文句柄example_support::load_fonts(cx)加载字体。查看 example_support/fonts.rs 可知在 wasm 目标下它用include_bytes!内嵌了 assets/fonts 中的 IBM Plex Sans 与 Lilex 字体文件并通过cx.text_system().add_fonts(...)注册在原生平台上该函数是空操作直接返回true原生平台使用系统字体所以每个示例都做了if !load_fonts(cx) { return; }的防御Bounds::centered(...)WindowOptions { window_bounds: ... }计算屏幕居中窗口边界并打开窗口窗口根视图通过cx.new(...)构造cx.activate(true)激活应用。此外每个示例文件头部都有一致的平台适配写法使同一个示例既能在原生平台、也能在 wasm 目标下运行#![cfg_attr(target_family wasm, no_main)] #[cfg(not(target_family wasm))] fn main() { run_example(); } #[cfg(target_family wasm)] #[wasm_bindgen::prelude::wasm_bindgen(start)] pub fn start() { gpui_platform::web_init(); run_example(); }这一点值得初学者留意从源码结构看GPUI 示例天然按平台无关的run_example 平台相关的入口来组织这是编写跨平台 GPUI 程序的基本模式。二、入门示例详解hello_worldGPUI 应用的最小形态hello_world展示了一个 GPUI 应用的基本形状创建Application、打开窗口、创建根视图、渲染一个div。其核心是实现了Rendertrait 的根视图结构体struct HelloWorld { text: SharedString, } impl Render for HelloWorld { fn render(mut self, _window: mut Window, _cx: mut ContextSelf) - impl IntoElement { div() .flex() .flex_col() .gap_3() .bg(rgb(0x505050)) .size(px(500.0)) .justify_center() .items_center() .shadow_lg() .border_1() .border_color(rgb(0x0000ff)) .text_xl() .text_color(rgb(0xffffff)) .child(format!(Hello, {}!, self.text)) // …下方还有一排彩色小方块演示 border_dashed / rounded_md 等修饰 } }从 hello_world.rs 的完整代码可以提取出三条要点根视图必须实现Render其render方法返回impl IntoElement参数依次是mut Window与mut ContextSelf样式采用链式调用命名明显借鉴 CSS/Tailwindflex()、flex_col()、gap_3()、size(px(500.0))、shadow_lg()、border_dashed()等颜色用rgb(0xRRGGBB)字面量如gpui::red()、gpui::white()视图持有状态这里是SharedString在render中读取并拼入元素树。input文本输入、焦点与键盘绑定input示例input.rs演示文本输入、焦点管理、选择区、剪贴板操作与键盘绑定是从静态渲染跨入可交互组件的推荐示例。另一个值得注意的进阶示例是view_example入口 view_example_main.rs运行命令cargo run -p gpui --example view_example。它的模块文档注释明确说明目的文本输入看似简单实则复杂而View原语让它易于组合并展示三层结构Editor是承担光标、闪烁、焦点与键盘处理的重活实体String是数据面通过editor.text(cx)读取Input/TextArea是整形层既可接收外部String也可接收Editor实体以便读取光标位置。其render中大量使用window.use_state(cx, ...)钩子在渲染时创建并持久化状态let name window.use_state(cx, |_, _| String::new()); let notes window.use_state(cx, |window, cx| Editor::new(multi\nline, window, cx));并在全局注册按键绑定与动作处理器cx.bind_keys([ KeyBinding::new(left, Left, None), KeyBinding::new(enter, Enter, None), KeyBinding::new(cmd-q, Quit, None), // … ]); cx.on_action(|_: Quit, cx| cx.quit());动作类型通过actions!(view_example, [Backspace, Delete, Left, Right, Home, End, Enter, Quit]);宏一次性声明。uniform_list最简单的虚拟列表虚拟列表是编辑器、文件树、聊天记录等长列表 UI 的基石。uniform_list示例uniform_list.rs展示了如何用最少的代码渲染一个 50 行的虚拟化列表div().size_full().bg(rgb(0xffffff)).child( uniform_list( entries, // 列表标识 50, // 总行数 cx.processor(|_this, range, _window, _cx| { let mut items Vec::new(); for ix in range { // range 只覆盖当前可见含缓冲的行 let item ix 1; items.push( div() .id(ix) .px_2() .cursor_pointer() .on_click(move |_event, _window, _cx| { println!(clicked Item {item:?}); }) .child(format!(Item {item})), ); } items }), ) .h_full(), )要点是uniform_list的processor闭包只在可见行区间range内构造元素滚动时按需回收与重建因此即使行数远大于 50 也不会有性能问题。若需要不等高行、表格化结构则参考交互分组的data_table示例——它将虚拟列表与表格行、自定义滚动条组合使用。三、布局与样式示例grid_layoutCSS Grid 风格的网格布局grid_layout示例grid_layout.rs用一个经典的圣杯布局Holy Grail Layout演示了 CSS-grid 风格的网格能力并且用container_query实现了响应式切换container_query(|container_size, _window, _cx| { // …构造 header / table_of_contents / content / ad / footer 五个块 if container_size.width px(400.) { container .flex() .flex_col() // 窄屏退化为单列纵向堆叠 .child(header.h_12().flex_none()) // … } else { container .grid() .grid_cols(5) .grid_rows(5) .child(header.row_span(1).col_span_full()) .child(content.col_span(3).row_span(3)) .child(ad.col_span(1).row_span(3)) .child(footer.row_span(1).col_span_full()) } })可以看到grid().grid_cols(n).grid_rows(n)定义网格col_span(n)/row_span(n)/col_span_full()控制单元格跨距而container_query回调接收实测的container_size据此在5×5 网格与Flex 纵向列之间切换——这正是拖拽窗口缩放时布局会变化的原因。其余样式示例速览README 将以下示例归入布局与样式分组opacity透明度样式pattern图案化背景patterned backgroundsshadow盒阴影box shadowstext带样式的文本渲染text_layout文本对齐、装饰线decoration、字重与换行行为text_wrapper文本内容换行。这些示例的共同价值在于展示 GPUI 文本与装饰 API 的实际效果边界适合作为调样式时的参考实现。四、交互示例交互分组覆盖八种常见交互模式每个示例都对应一个可独立运行的窗口anchor锚点定位anchored positioning即元素相对某个参照点定位data_table虚拟列表 表格行 自定义滚动条的组合drag_drop可拖拽元素与放置目标drop targetsfocus_visible键盘可见的焦点样式focus styling用于保证键盘导航的可访问性mouse_pressure压力感应指针输入在支持的设备上popover浮层演示deferred与anchored两种浮层形态scrollable可滚动内容tab_stop键盘 Tab 导航。这些示例配合view_example中的Input/TextArea组合基本覆盖了构建桌面应用所需的交互组件面。五、图片、绘制与动画示例animationGPUI 动画与带动画的 SVG transformgif_viewerGIF 渲染演示素材即目录下的 black-cat-typing.gifgradient线性渐变与色彩空间color spacesimage入口 image/image.rs本地与远程图片加载、图片尺寸、资源asset配置image_gallery图片缓存与远程图片加载image_loading图片加载状态与资源加载painting基于路径paths与 canvas 的自绘制svg入口 svg/svg.rsSVG 渲染。从目录结构看image与svg是仅有的两个带配套素材子目录的示例素材与代码分离、按需引用的方式与 Zed 主仓库管理 assets 的思路一致。六、窗口与应用行为示例move_entity_between_windows将实体entity在两个窗口之间迁移是理解 GPUI 实体-窗口所有权模型的关键示例on_window_close_quit窗口关闭时退出应用的钩子set_menus应用程序菜单配置system_notifications操作系统通知的发布、替换、关闭与响应window创建普通窗口、对话框dialog、弹出层popup与浮动窗口floatingwindow_positioning窗口边界bounds与位置控制window_shadow窗口阴影样式。window示例尤其重要WindowOptions的不同组合决定了窗口的形态而 hello_world.rs 中window_bounds: Some(WindowBounds::Windowed(bounds))只是其中普通窗口的最简用法。七、专项示例面向 GPUI 自身开发者的工具README 特别注明这一组示例在开发 GPUI 本身时有用但不一定是新应用的最好起点active_state_bug一个聚焦的 active-state 问题复现layer_shellLinux layer-shell 窗口list_example底部对齐的列表状态与滚动条行为ownership_post支撑所有权与数据流ownership and data flow文档的示例paths_bench路径渲染基准测试对应 gpui 的bench-supportfeaturetree渲染深度嵌套的元素树用于压力测试布局/绘制。如果你只是写应用可以先跳过这一组专注于前四组示例。八、深入 testing 示例GPUI 测试基础设施testing示例testing.rs是全部示例中信息密度最高的一个正常运行时它是一个可交互的计数器窗口而在test-supportfeature 下其#[cfg(test)]模块系统性地演示了 GPUI 的测试能力。一个Counter实体贯穿所有测试它带FocusHandle、实现了EventEmitterCounterEvent、通过cx.subscribe_self订阅自身事件收到事件时把count置为 999并演示了cx.spawn异步任务load/reload。基本测试#[gpui::test]TestAppContext#[gpui::test] fn basic_testing(cx: mut TestAppContext) { let counter cx.new(|cx| Counter::new(cx)); counter.update(cx, |counter, _| { counter.count 42; }); // 注意TestAppContext 不支持 read(cx)要用 read_with let updated counter.read_with(cx, |counter, _| counter.count); assert_eq!(updated, 42); // 事件订阅是同步副作用update 完成后立即执行 counter.update(cx, |_, cx| { cx.emit(CounterEvent); }); let count_after_update counter.read_with(cx, |counter, _| counter.count); assert_eq!(count_after_update, 999, Side effects should run after update completes); }这里暴露了 GPUI 测试的第一条核心语义同步副作用事件订阅、通知回调在你的update调用完成之后立即执行因此断言必须写在同一批次 update 之后。另注意TestAppContext下读取实体要用read_with(cx, ...)而非read(cx)。窗口测试VisualTestContext涉及窗口的测试需要构造VisualTestContext。与同步副作用同理窗口会在每次update*调用之后被重绘因此可以测试渲染相关的行为#[gpui::test] fn test_counter_in_window(cx: mut TestAppContext) { let window cx.update(|cx| { cx.open_window(Default::default(), |_, cx| cx.new(|cx| Counter::new(cx))).unwrap() }); let mut cx VisualTestContext::from_window(window.into(), cx); let counter cx.root(mut cx).unwrap(); // 动作分派依赖元素树解析处理器测试中行为与运行时一致 let focus_handle counter.read_with(cx, |counter, _| counter.focus_handle.clone()); cx.update(|window, cx| { focus_handle.dispatch_action(Increment, window, cx); }); let count_after counter.read_with(cx, |counter, _| counter.count); assert_eq!(count_after, 1); }这个测试同时验证了动作action机制dispatch_action经由焦点句柄分派走的是与真实键盘事件相同的元素树解析路径。运行时对应的是 testing.rs 中cx.bind_keys把up/down键绑定到Increment/Decrement、并以key_context(Counter)限定作用域。异步测试与run_until_parkedGPUI 测试可以是async的。测试执行器是单线程的所以异步副作用包括后台任务不会自动运行必须显式让出控制#[gpui::test] async fn test_async_operations(cx: mut TestAppContext) { let counter cx.new(|cx| Counter::new(cx)); // 任务可以直接 await counter.update(cx, |counter, cx| counter.load(cx)).await; assert_eq!(counter.read_with(cx, |c, _| c.count), 100); // 但 detached 任务要等 run_until_parked 才会执行 counter.update(cx, |counter, cx| counter.reload(cx)); assert_eq!(counter.read_with(cx, |c, _| c.count), 100); cx.run_until_parked(); // 运行所有挂起任务 assert_eq!(counter.read_with(cx, |c, _| c.count), 150); }另外测试执行器会在await 一个等待 GPUI 控制之外的事物的 future时 panic例如读文件、网络 IO用于帮助发现潜在死锁若确需等待外部系统调用cx.executor().allow_parking()关闭该检查示例中用std::thread::spawn oneshot channel 模拟了外部文件系统#[gpui::test] async fn test_allow_parking(cx: mut TestAppContext) { cx.executor().allow_parking(); let (tx, rx) futures::channel::oneshot::channel(); std::thread::spawn(move || { /* 5ms 后发送 42 */ }); let result rx.await.unwrap(); assert_eq!(result, 42); }属性测试与多应用分布式系统测试#[gpui::test(iterations 10)]加上一个StdRng参数即可获得属性测试支持示例用它随机执行 100 次加/减操作并断言计数结果与预期一致。更特别的是多应用上下文测试——测试函数接收多个TestAppContext参数即可模拟分布式系统#[gpui::test] fn test_app_sync(cx_a: mut TestAppContext, cx_b: mut TestAppContext) { let network MockNetwork::new(); let a cx_a.new(|_| NetworkedCounter::new(network.a_client())); let b cx_b.new(|_| NetworkedCounter::new(network.b_client())); b.update(cx_b, |b, cx| b.increment(42, cx)); // B 本地立即生效 a.read_with(cx_a, |a, _| assert_eq!(a.count, 0)); // A 尚未收到 cx_b.run_until_parked(); // 消息送达 mock 网络 a.update(cx_a, |a, _| a.sync()); // A 拉取增量 a.read_with(cx_a, |a, _| assert_eq!(a.count, 42)); }配套说明指出多个应用上下文共享一个调度器每次run_until_parked时调度器随机选择先运行哪个应用的任务因此test_random_interleaving同样带iterations 10可以验证你的分布式代码对不同的执行顺序都健壮。这套机制与 Zed 编辑器本身的多用户协作场景在测试方法论上是一脉相承的。九、小结如何按此路径学习 GPUI基于 README 的分类与各示例源码推荐的学习顺序是先跑通hello_world理解application().run→open_window→cx.new→Render::render的骨架与 wasm/原生双入口写法用input与view_example建立交互心智模型实体Editor、数据面String、整形层Input/TextArea、use_state、bind_keys与actions!用uniform_list掌握长列表虚拟化的uniform_listcx.processor模式需要表格形态时再看data_table按需浏览布局grid_layout、图片image、svg、gif_viewer与窗口window、set_menus示例补齐特定能力最后精读testing掌握#[gpui::test]、TestAppContext/VisualTestContext、run_until_parked、allow_parking与多上下文分布式测试并用cargo test -p gpui --example testing --features test-support验证。所有示例均为只读运行cargo run -p gpui --example name即可查看效果无需修改仓库任何内容。【免费下载链接】zedCode at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.项目地址: https://gitcode.com/GitHub_Trending/ze/zed创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →