| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
root: Add Base window hosting and a single Kit startup entry point (#3152) ## Summary Window startup now always mounts a `gpui_base::Root`, regardless of Cargo feature unification. Base owns application content, overlay hosting, keyboard traversal, and text-selection copying. Component initialization registers its per-window presentation state as a Root plugin, so dialogs, sheets, notifications, menus, tooltips, touch selection, and window chrome remain automatic without making Base depend on Component. - Add `gpui_kit::open_window` as the standard Kit application entry point. It always mounts Base Root and returns both the window handle and content entity. - Move Root ownership and unconditional overlay hosting into `gpui-base`; `gpui_component::Root` is now a re-export. Root now renders sheet, dialog, and notification layers automatically. - Add the typed `RootPlugin` interface. Plugins are registered before window creation, instantiated independently per window, rendered in registration order, and can prepare, style, and decorate the root surface. - Keep Component presentation in a `WindowState` Root plugin while application-facing operations remain on `WindowExt`. - Make `window_border()` solely responsible for client-side window chrome. Server-decorated windows pass content through unchanged; client-decorated windows receive borders, shadow inset, rounded corners, and resize hit zones. - Remove `Root::bordered` and `Root::window_shadow_size`, plus the obsolete `root_borderless` example. - Migrate Kit examples, stories, tests, documentation, and pending 0.7.0 release notes to the new startup and Root APIs. ## Public API ### gpui-base ```rust pub trait RootPlugin: Render + Sized { fn prepare(&mut self, window: &mut Window, cx: &mut Context<Self>) {} fn style(&self, surface: &mut Stateful<Div>, window: &mut Window, cx: &mut App) {} fn decorate( &self, surface: AnyElement, root: &Root, window: &mut Window, cx: &mut App, ) -> impl IntoElement { surface } } impl Root { pub fn new( view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>, ) -> Self; pub fn register_plugin<V: RootPlugin>( cx: &mut App, build: fn(&mut Window, &mut Context<V>) -> V, ); pub fn plugin<V: RootPlugin>(&self) -> Option<Entity<V>>; pub fn view(&self) -> &AnyView; pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self; pub fn update<R>( window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R, ) -> R; } ``` `Root` also implements `Styled`. Instance style refinements are applied after plugin defaults and therefore take precedence: ```rust impl Styled for Root { fn style(&mut self) -> &mut StyleRefinement; } ``` Register plugins during explicit application initialization, before creating windows. Re-registering a plugin type replaces its factory for future windows rather than mounting it twice. Registration does not retrofit existing roots. ### gpui-kit ```rust pub fn open_window<V: Render>( options: WindowOptions, cx: &mut App, build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, ) -> Result<(AnyWindowHandle, Entity<V>)>; ``` The builder returns application content, not a Root. Kit mounts Base Root around it. ### gpui-component `pub use gpui_base::Root;` replaces the former Component-owned Root. `gpui_component::init(cx)` registers Component `WindowState` as a Root plugin. Manual layer-rendering and Root-owned dialog, sheet, and notification operations are removed. In particular, `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` no longer exist because Root hosts those layers automatically. Use the existing `WindowExt` operations to open and update them. ## Breaking Changes Targeted for 0.7.0; package versions remain unchanged. Use the Kit window entry point and return application content instead of constructing `Root` manually. Retain the returned content entity when direct content access is needed, because the window root is now `gpui_base::Root`: ```diff - cx.open_window(options, |window, cx| { - let content = build_content(window, cx); - cx.new(|cx| Root::new(content, window, cx)) - }) + let (window_handle, content) = + gpui_kit::open_window(options, cx, |window, cx| { + build_content(window, cx) + })?; ``` Delete manual overlay placement. `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` are removed because `Root` now hosts these layers automatically: ```diff - let sheet_layer = Root::render_sheet_layer(window, cx); - let dialog_layer = Root::render_dialog_layer(window, cx); - let notification_layer = Root::render_notification_layer(window, cx); - div() .child(content) - .children(sheet_layer) - .children(dialog_layer.map(|layer| deferred(layer).with_priority(1))) - .children(notification_layer) ``` Root window-chrome configuration is removed. Decoration policy comes from GPUI `WindowOptions`; `window_border()` applies client chrome only for `Decorations::Client`: ```diff - Root::bordered - Root::window_shadow_size + window_border() ``` The Root- and WindowExt-owned text-selection methods are removed in favor of `gpui_base::TextSelection`: ```diff - window.selected_text(cx) + TextSelection::selected_text(window, cx) - window.has_text_selection(cx) + TextSelection::has_selection(window, cx) - root.clear_text_selection(window, cx) - window.clear_text_selection(cx) + TextSelection::clear(window, cx) - window.end_text_selection(cx) + TextSelection::end(window, cx) ``` The remaining Component-owned Root operations are removed in favor of the corresponding `WindowExt` methods: ```diff - root.open_dialog(build, window, cx) + window.open_dialog(cx, build) - root.close_dialog(window, cx) + window.close_dialog(cx) - root.close_all_dialogs(window, cx) + window.close_all_dialogs(cx) - root.open_sheet_at(placement, build, window, cx) + window.open_sheet_at(placement, cx, build) - root.close_sheet(window, cx) + window.close_sheet(cx) - root.push_notification(notification, window, cx) + window.push_notification(notification, cx) - root.remove_notification::<T>(window, cx) + window.remove_notification::<T>(cx) - root.remove_notification1::<T>(key, window, cx) + window.remove_notification1::<T>(key, cx) - root.clear_notifications(window, cx) + window.clear_notifications(cx) ``` ## Test Plan - `cargo test -p gpui-base --lib` — 993 passed. - `cargo test -p gpui-component --lib` — 559 passed. - `cargo test -p gpui-kit --features test-support,component,assets --test root` — 6 passed. - `cargo test -p gpui-kit --features test-support,component,assets --tests` — 114 passed during the window-startup migration. - `cargo test -p gpui-kit --features test-support,component,assets --test rendering` — 2 Metal pixel checks passed. - `cargo clippy -p gpui-base -p gpui-component -p gpui-kit --all-targets --features gpui-kit/test-support -- --deny warnings` — passed. - `cargo check -p gpui-kit --no-default-features` — passed. - `cargo fmt --all --check` — passed. - `git diff --check` — passed. - `script/check-ai-recipes` — 9 published recipe fragments passed. AI-assisted changes prepared with Codex. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> | 14 天前 | |
editor: Measure the completion prefix from the typed text and drop stale trigger offsets (#3363) Closes #3081 ## Description `InputBaseState::<EditorMode>::handle_completion_trigger` hands the completion provider the wrong `trigger_character` in two ways, both reported in #3081 with repro steps against `examples/editor`. **The replacement was measured from `range.end`.** That only locates the typed text for a collapsed caret. When a keystroke replaces a span (typing over a selection, an IME commit) the new text begins at `range.start`, so an equally long replacement measured an empty prefix and a longer one made `new_offset < start_offset` true and dropped the request before the provider was called. The handler now derives the inserted text's start from the post-edit active caret and the normalized input length. This also accounts for preceding multi-cursor edits shifting the active insertion, while preserving selection replacements and IME commits. **`trigger_start_offset` was a latch that nothing invalidated.** Once a menu opened it was reused by every later keystroke: not a deletion, not `hide_context_menu()`, not a document switch cleared it. Typing a character at offset 0 latched `Some(0)`, and from then on every keystroke anywhere measured `text[0..caret]`. The latch now applies only while the edit continues the word it was opened for: the document between the latched offset and the edit must still read as a prefix of the last recorded query. Deleting back into the word keeps refining the same query (`pri` → backspace → `v` still asks for `prv`); typing somewhere else, or into a replaced document, starts a new query at the edit. The check lives in the handler rather than in `hide_context_menu()` because `replace_text_in_ranges` hides the menu on every edit, including the keystroke that is about to refine the query; clearing the latch there would reduce every query to its last character. `present_completion_items` and the component menu's own `trigger_start_offset` are unchanged. ## How to Test ```sh cargo test -p gpui-kit --features "test-support component" --test input completions:: ``` New cases in `crates/kit/tests/input/completions.rs`: - `typing_over_a_selection_measures_the_prefix_from_where_the_text_begins`: without the fix the longer replacement sends no request and the equal-length one sends `trigger_character: ""`. - `typing_elsewhere_does_not_reuse_a_stale_trigger_offset`: without the fix the provider receives `"!\na"` instead of `"a"`. - `replacing_the_document_starts_a_new_query_at_the_edit`: without the fix the provider receives `"hellox"` instead of `"x"`. - `deleting_back_into_the_word_keeps_refining_the_same_query`: guards the refinement path the latch exists for; passes before and after. All 45 `completions::` tests pass. A follow-up regression, `test_multicursor_unicode_completion_uses_post_edit_start` in Base, reproduces a UTF-8 boundary panic when another cursor inserts Chinese text before the active cursor. It failed before the follow-up fix and passes afterward, checking both the provider's post-edit offset and query. The handler also validates both slice endpoints. Local follow-up validation passed: all 1,307 Base library tests, all 45 Kit `completions::` tests, targeted Base/Kit Clippy with warnings denied, and formatting/diff checks. Follow-up implementation and regression test were AI-assisted with Codex. CI for the follow-up commit is pending. Also run: `cargo clippy -p gpui-base -p gpui-kit --features "gpui-kit/test-support gpui-kit/component" --all-targets -- --deny warnings`, `cargo fmt --check`, and `cargo test -p gpui-component --lib input::` for the overlay that mirrors the trigger offset. Not a visual change, so no screenshots. The headless UI tests above cover the behavior; I did not run the story app for this. AI assistance: the analysis, patch and tests were drafted with Claude (Fable 5.1) in Claude Code and reviewed by me before submission. ## Checklist - [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and followed the guidelines. - [x] Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate. - [ ] Passed `cargo run` for story tests related to the changes. (Not run: no story exercises completion triggers; covered by the headless `completions::` tests.) - [x] Tested macOS, Windows and Linux platforms performance (if the change is platform-specific). Not platform-specific, but verified anyway: the PR's CI run [37157842358](https://github.com/longbridge/gpui-kit/actions/runs/37157842358) passed `Test (aarch64-apple-darwin, macos-latest)`, `Test (x86_64-linux-gnu, ubuntu-latest)` and `Test (x86_64-windows-msvc, windows-latest)` along with Lint, No default features and the GPUI Shell jobs; locally the 45 `completions::` tests pass on macOS 15 (Apple Silicon) and inside a Linux container (`rust:1-bookworm`, aarch64, rustc 1.99.0, deps from `script/install-linux.sh`). ### Follow-up: asynchronous completion menu refresh A reported `c` → `co` workflow exposed a state-feedback race: synchronizing a hidden Component popup scheduled a dismissal back to Base, which could close a newer completion response. Presentation-only hiding now leaves Base untouched; explicit user dismissal still notifies Base. A deterministic regression failed before this fix and passes afterward. Also verified 46 Kit completion workflows, 40 Component input tests, targeted Component/Kit Clippy, formatting/diff checks, and an editor example rebuild. Follow-up code and tests were AI-assisted with Codex. CI for this revision is pending. --------- Co-authored-by: abderbj <115119179+abderbj@users.noreply.github.com> Co-authored-by: Jason Lee <huacnlee@gmail.com> | 2 天前 | |
assets: share Lucide icon names and preserve default icons (#3020) ## Description Provide the complete Lucide catalog through `gpui-kit-assets::IconName` so Base and other presentation layers can share it without depending on GPUI Component. The package includes all 1,818 Lucide 1.43.0 SVGs and the 12 retained GPUI Kit icons. Keep the existing application workflow: default `Assets` embeds the original 101 component icons, and applications provide additional icons through their own `AssetSource`. Full native embedding is an explicit `AllAssets` choice. The optional `icon_assets!(ExtraIcons, [...])` macro creates a selected source that can be composed with the default bundle. WASM keeps its existing on-demand CDN loader; selected sources can embed bytes directly. ## Compatibility Existing component imports, exhaustive matches, and `IconName::Search.view(cx)` calls remain valid without additional trait imports. Component retains its original enum as a compatibility adapter; `Icon::new(...)` accepts both this enum and the new shared assets enum. Legacy names can convert into shared names using `.into()`. The extension trait is only an optional convenience for the new shared enum. Preserve `Assets::get`, `Assets::iter`, `Assets::new`, the original default path set, asset-source behavior, and Cargo icon-directory metadata. The complete catalog and default component bundle are generated separately; adding Lucide files does not expand default embedding. ## Documentation and maintenance README and both language versions of Icon / Icons & Assets have a prominent NOTE distinguishing the catalog from default embedding, plus a measured comparison table. A default-plus-10-extra-icons example adds 19,648 bytes to a Linux release/stripped resource program; those extra SVGs total 3,903 bytes. The measurement includes source composition/lookup code and is not a fixed per-icon cost or a RAM estimate. `bun script/sync-lucide.ts` replaces the Python updater. It verifies the pinned archive SHA-256, supports offline `--archive` and read-only `--check`, copies the upstream license, and preserves custom icons and the default bundle selection. ## Validation - `cargo test -p gpui-kit-assets --test icons`: 5 tests covering complete/default catalogs, selected resources, empty selection and native compatibility. - `cargo test -p gpui-kit --test assets`: 2 tests covering shared names and unchanged legacy views/conversions; 1 test also passes with `--no-default-features --features assets`. - `cargo clippy -p gpui-component -p gpui-kit-assets -p gpui-component-story -- --deny warnings`; `cargo fmt --all --check`. - `cargo check -p gpui-component --no-default-features`. - `cargo check -p gpui-kit-assets --lib --example selected_assets --target wasm32-unknown-unknown`. - `cargo package -p gpui-kit-assets --allow-dirty` verifies packaged build generation. - `cargo run -p gpui-kit-assets --release --example extra_assets -- icons/accessibility.svg` and `icons/search.svg` verify application and component fallback paths. - `bun test script/tests/sync-lucide.test.ts`: 14 assertions covering check-only behavior, synchronization, custom-file preservation, count validation and corrupt-archive rejection. Online and offline checks verify all 1,818 upstream SVGs and their license. - Website production build passes; generated HTML contains the comparison table inside each of the four NOTE callouts. - Binary inspection confirms 101 SVG payloads by default, 103 with two extras, 111 with ten extras, and 1,830 only with explicit `AllAssets`. | Native resource configuration | Embedded SVG size (KiB) | Binary increase over default (KiB) | | --- | ---: | ---: | | Default (101 icons) | 44.28 | 0.00 | | Default + 2 extras | 45.04 | 15.19 | | Default + 10 extras | 48.09 | 19.19 | | Explicit AllAssets | 731.45 | 1045.16 | 1 KiB = 1,024 bytes. Measured with Rust 1.98.0 on Linux x86_64, release optimization and stripped symbols. Every program retains the same shared-name lookup and runtime path; extra sources fall back to the default and merge/sort/deduplicate their lists. The documentation names the exact icons and explains limitations. ## Checklist - [x] Read the contribution guidelines. - [x] Reviewed the implementation, including AI-generated code. - [x] Ran targeted tests and resource examples. - [ ] Ran the interactive story gallery. - [ ] Tested native macOS and Windows behavior (Linux and WASM compilation validated). | 28 天前 | |
root: Add Base window hosting and a single Kit startup entry point (#3152) ## Summary Window startup now always mounts a `gpui_base::Root`, regardless of Cargo feature unification. Base owns application content, overlay hosting, keyboard traversal, and text-selection copying. Component initialization registers its per-window presentation state as a Root plugin, so dialogs, sheets, notifications, menus, tooltips, touch selection, and window chrome remain automatic without making Base depend on Component. - Add `gpui_kit::open_window` as the standard Kit application entry point. It always mounts Base Root and returns both the window handle and content entity. - Move Root ownership and unconditional overlay hosting into `gpui-base`; `gpui_component::Root` is now a re-export. Root now renders sheet, dialog, and notification layers automatically. - Add the typed `RootPlugin` interface. Plugins are registered before window creation, instantiated independently per window, rendered in registration order, and can prepare, style, and decorate the root surface. - Keep Component presentation in a `WindowState` Root plugin while application-facing operations remain on `WindowExt`. - Make `window_border()` solely responsible for client-side window chrome. Server-decorated windows pass content through unchanged; client-decorated windows receive borders, shadow inset, rounded corners, and resize hit zones. - Remove `Root::bordered` and `Root::window_shadow_size`, plus the obsolete `root_borderless` example. - Migrate Kit examples, stories, tests, documentation, and pending 0.7.0 release notes to the new startup and Root APIs. ## Public API ### gpui-base ```rust pub trait RootPlugin: Render + Sized { fn prepare(&mut self, window: &mut Window, cx: &mut Context<Self>) {} fn style(&self, surface: &mut Stateful<Div>, window: &mut Window, cx: &mut App) {} fn decorate( &self, surface: AnyElement, root: &Root, window: &mut Window, cx: &mut App, ) -> impl IntoElement { surface } } impl Root { pub fn new( view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>, ) -> Self; pub fn register_plugin<V: RootPlugin>( cx: &mut App, build: fn(&mut Window, &mut Context<V>) -> V, ); pub fn plugin<V: RootPlugin>(&self) -> Option<Entity<V>>; pub fn view(&self) -> &AnyView; pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self; pub fn update<R>( window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R, ) -> R; } ``` `Root` also implements `Styled`. Instance style refinements are applied after plugin defaults and therefore take precedence: ```rust impl Styled for Root { fn style(&mut self) -> &mut StyleRefinement; } ``` Register plugins during explicit application initialization, before creating windows. Re-registering a plugin type replaces its factory for future windows rather than mounting it twice. Registration does not retrofit existing roots. ### gpui-kit ```rust pub fn open_window<V: Render>( options: WindowOptions, cx: &mut App, build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, ) -> Result<(AnyWindowHandle, Entity<V>)>; ``` The builder returns application content, not a Root. Kit mounts Base Root around it. ### gpui-component `pub use gpui_base::Root;` replaces the former Component-owned Root. `gpui_component::init(cx)` registers Component `WindowState` as a Root plugin. Manual layer-rendering and Root-owned dialog, sheet, and notification operations are removed. In particular, `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` no longer exist because Root hosts those layers automatically. Use the existing `WindowExt` operations to open and update them. ## Breaking Changes Targeted for 0.7.0; package versions remain unchanged. Use the Kit window entry point and return application content instead of constructing `Root` manually. Retain the returned content entity when direct content access is needed, because the window root is now `gpui_base::Root`: ```diff - cx.open_window(options, |window, cx| { - let content = build_content(window, cx); - cx.new(|cx| Root::new(content, window, cx)) - }) + let (window_handle, content) = + gpui_kit::open_window(options, cx, |window, cx| { + build_content(window, cx) + })?; ``` Delete manual overlay placement. `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` are removed because `Root` now hosts these layers automatically: ```diff - let sheet_layer = Root::render_sheet_layer(window, cx); - let dialog_layer = Root::render_dialog_layer(window, cx); - let notification_layer = Root::render_notification_layer(window, cx); - div() .child(content) - .children(sheet_layer) - .children(dialog_layer.map(|layer| deferred(layer).with_priority(1))) - .children(notification_layer) ``` Root window-chrome configuration is removed. Decoration policy comes from GPUI `WindowOptions`; `window_border()` applies client chrome only for `Decorations::Client`: ```diff - Root::bordered - Root::window_shadow_size + window_border() ``` The Root- and WindowExt-owned text-selection methods are removed in favor of `gpui_base::TextSelection`: ```diff - window.selected_text(cx) + TextSelection::selected_text(window, cx) - window.has_text_selection(cx) + TextSelection::has_selection(window, cx) - root.clear_text_selection(window, cx) - window.clear_text_selection(cx) + TextSelection::clear(window, cx) - window.end_text_selection(cx) + TextSelection::end(window, cx) ``` The remaining Component-owned Root operations are removed in favor of the corresponding `WindowExt` methods: ```diff - root.open_dialog(build, window, cx) + window.open_dialog(cx, build) - root.close_dialog(window, cx) + window.close_dialog(cx) - root.close_all_dialogs(window, cx) + window.close_all_dialogs(cx) - root.open_sheet_at(placement, build, window, cx) + window.open_sheet_at(placement, cx, build) - root.close_sheet(window, cx) + window.close_sheet(cx) - root.push_notification(notification, window, cx) + window.push_notification(notification, cx) - root.remove_notification::<T>(window, cx) + window.remove_notification::<T>(cx) - root.remove_notification1::<T>(key, window, cx) + window.remove_notification1::<T>(key, cx) - root.clear_notifications(window, cx) + window.clear_notifications(cx) ``` ## Test Plan - `cargo test -p gpui-base --lib` — 993 passed. - `cargo test -p gpui-component --lib` — 559 passed. - `cargo test -p gpui-kit --features test-support,component,assets --test root` — 6 passed. - `cargo test -p gpui-kit --features test-support,component,assets --tests` — 114 passed during the window-startup migration. - `cargo test -p gpui-kit --features test-support,component,assets --test rendering` — 2 Metal pixel checks passed. - `cargo clippy -p gpui-base -p gpui-component -p gpui-kit --all-targets --features gpui-kit/test-support -- --deny warnings` — passed. - `cargo check -p gpui-kit --no-default-features` — passed. - `cargo fmt --all --check` — passed. - `git diff --check` — passed. - `script/check-ai-recipes` — 9 published recipe fragments passed. AI-assisted changes prepared with Codex. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> | 14 天前 | |
kit: Add optional gpui-fast backend (#3375) Add an optional `gpui-fast` feature across GPUI Kit’s existing crates: ```toml gpui-kit = { version = "0.7", features = ["gpui-fast"] } ``` No new crates. Preserve the existing `gpui-pre-*` workspace dependencies and default backend; optional Fast dependencies and internal conditional aliases select the engine. Existing application imports and macros continue to work. This is additive, with no breaking changes. Upstream packages still compile because Cargo features are additive. ## Public API - **gpui-kit:** `gpui-fast` selects Fast for core, platforms, and enabled layers. - **gpui-base, gpui-component, gpui-kit-assets, gpui-fps, gpui-wry, gpui-shell, gpui-component-shell:** `gpui-fast` selects the matching backend for standalone consumers. - **gpui-component-macros:** `#[derive(IntoPlot)]` additionally recognizes direct Fast dependencies. - Gallery and examples expose the same feature for comparisons. ## Validation 27 focused UI/macro/assets tests and 9 documentation tests pass. Default/Fast configurations, examples, WebView, Shell, benchmarks, and nightly WebAssembly compile. Clippy, formatting, dependency, and snapshot-pin checks pass. CI adds native and WebAssembly coverage. AI-assisted with Codex. | 20 小时前 | |
radio: Keep a disabled Radio disabled inside a RadioGroup (#3331) Closes #3324 ## Description `RadioGroup::render` applied the group's `disabled` flag to every child with `radio.disabled(disabled)`, which overwrote a Radio's own `.disabled(true)`. A disabled option in an enabled group looked enabled and could be selected. An item is now disabled when it or the group is: `radio.disabled(disabled || item_disabled)`. A disabled group still disables every item. ## Screenshot Screen recordings on Android (Redroid, dark theme) of a `RadioGroup` whose "XL" item is `.disabled(true)`, tapping XL, then M, then XL, Kit 0.7.0 with and without this diff. **Before** https://github.com/user-attachments/assets/ecc185e1-f8b2-4505-b024-017922536b13 **After** https://github.com/user-attachments/assets/2b7c3cfb-912c-4618-a43e-eb85f9cd3a81 Before: XL renders enabled and each tap selects it (`horizontal=Some(3)`). After: XL renders disabled and taps do nothing, while M still selects normally. Builds: [demo-pr3331](https://github.com/Bombatomica64/Gpui-android/releases/tag/demo-pr3331). ## How to Test - New `radio_group_keeps_a_disabled_item_disabled` in `crates/kit/tests/controls.rs`: clicking a disabled item in an enabled group leaves the selection unchanged, clicking an enabled item selects it, and in a disabled group an enabled item cannot be selected. On `main` it fails with the disabled item selected (`Some(1)`). - `cargo test -p gpui-kit --features test-support,component --test controls`: 8 passed. - `cargo test -p gpui-component --features test-support --lib radio`: 2 passed. Note: I first tried to assert on `TestElement::disabled()`, but it is not populated for Radio or Checkbox (the existing `locked` Checkbox test also expects `None`), so the test checks behaviour instead. ## Checklist - [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and followed the guidelines. - [x] Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate. - [ ] Passed `cargo run` for story tests related to the changes. (Not run: no desktop session here; see How to Test.) - [ ] Tested macOS, Windows and Linux platforms performance (if the change is platform-specific) (not platform-specific) Thanks for taking the time to review this. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> | 5 天前 | |
date_picker: Support editing the time of day with `TimeField` (#3206) ## Summary DatePicker can now edit the time of day, down to minutes or seconds, on a 24-hour (default) or 12-hour clock. ```rust DatePickerState::new(window, cx) .time_precision(TimePrecision::Second) // or Minute; unset = date only, unchanged .hour_cycle(HourCycle::H12) // default H23 .default_time(NaiveTime::from_hms_opt(9, 0, 0).unwrap()) ``` - A time field appears below the calendar. Picking a date keeps the popup open; every date or time edit emits `Change` immediately. Clicking the selected date again (so double-clicking a date) closes the popup without emitting, as do Enter, Escape and a click outside. - The display format follows the precision and hour cycle (e.g. `%Y/%m/%d %H:%M:%S`, `%Y/%m/%d %I:%M %p`) unless `date_format` is set. - A range picker edits dates only, even with a `time_precision`; a range with times is composed from two single pickers (see docs). - Enter now also closes an open picker (previously it did nothing while open). - The time field and, while editing times, the trigger use tabular figures (`tnum`) so digits keep their width while typing; the AM/PM segment stacks both labels so it keeps the wider one's width. The 24-hour default follows fixed-format libraries (Ant Design, Element) and the conventions of our Chinese-market and trading UIs; GPUI does not expose the system's hour-cycle preference that native pickers follow. ### New `TimeField` - `gpui-base` (**Base-layer change**): unstyled `TimeField` / `TimeFieldState` owning focus, the keyboard model and segment editing (`Role::TimeInput`). - Up/Down step the selected segment and wrap without carry; Left/Right and Tab/Shift-Tab move between segments; digits type with a two-digit buffer and auto-advance; `a`/`p` set AM/PM; Backspace resets the segment. - `gpui-component`: styled `TimeField`, usable on its own in forms. - Docs: `website/component/time-field.md` and `website/base/primitives/time-field.md` (en + zh-CN), catalog entries, the gpui-kit skill, a `TimeField` gallery story and a `time-field` base showcase. ## Public API ### `gpui-base` - `pub struct TimeFieldState` — retained editing behavior for a segmented time field. - `pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self` - `pub fn precision(self, precision: TimePrecision) -> Self` / `pub fn set_precision(&mut self, precision: TimePrecision, window, cx)` — minutes or seconds. - `pub fn hour_cycle(self, hour_cycle: HourCycle) -> Self` / `pub fn set_hour_cycle(&mut self, hour_cycle: HourCycle, window, cx)` — 24- or 12-hour clock. - `pub fn time(&self) -> NaiveTime` / `pub fn set_time(&mut self, time: NaiveTime, window, cx)` — read or replace the value without emitting. - `pub fn selected_segment(&self) -> TimeSegment` — the segment keyboard editing applies to. - `pub fn focus(&self, window: &mut Window, cx: &mut App)` — focus the field. - `pub enum TimeFieldEvent { Change(NaiveTime) }` — emitted on user edits. - `pub enum TimePrecision { Minute, Second }` — `format(self, HourCycle) -> &'static str`, `truncate(self, NaiveTime) -> NaiveTime`. - `pub enum HourCycle { H23, H12 }` — how the hours of a day are counted; names follow Unicode `hourCycle`. - `pub enum TimeSegment { Hour, Minute, Second, Period }` — one editable component. - `pub struct TimeField` — unstyled element: `new(id, &Entity<TimeFieldState>)`, `disabled(bool)`, `render_segment(renderer)`, `Styled`. - `pub struct TimeFieldSegment` — pre-wired segment passed to the segment slot (`Styled`, `ParentElement`, `InteractiveElement`); `clear_children()` removes the default label. - `pub struct TimeFieldSegmentState` — `segment()`, `value()`, `is_selected()`, `is_disabled()` for decorating a segment. ### `gpui-component` - `time_field::TimeField` — styled time field: `new(&Entity<TimeFieldState>)`, `invalid(bool)`, `Sizable`, `Disableable`, `Styled`. Re-exports `TimeFieldState`, `TimeFieldEvent`, `TimePrecision`, `HourCycle`, `TimeSegment`. - `date_picker::DateTime` — `Single(Option<NaiveDateTime>)` / `Range(Option<NaiveDateTime>, Option<NaiveDateTime>)`, with `is_some`, `is_complete`, `start`, `end`, `date() -> Date`, `format(&str)`. - `DatePickerState::time_precision(self, TimePrecision) -> Self` — edit the time of day too (single mode). - `DatePickerState::hour_cycle(self, HourCycle) -> Self` — 24- or 12-hour time field. - `DatePickerState::default_time(self, NaiveTime) -> Self` — the time a date gets before it is edited. - `DatePickerState::date_time(&self) -> DateTime` / `set_date_time(&mut self, impl Into<DateTime>, window, cx)` — the whole value. - `DateRangePreset::date_time(label, impl Into<DateTime>) -> Self` and `DateRangePresetValue::DateTime(DateTime)` — presets with times. ### `gpui-component-shell` (JavaScript, `gpui-component` module) - `TimeFieldState(): TimeFieldState` — retained 24-hour, minute-precision time state. - `new TimeField(state: TimeFieldState): TimeFieldElement` — segmented time field; `.disabled(disabled: boolean)`. - Mirrored in the JS gallery (`time-field` route) and `component-inventory.json`. ## Breaking Changes `DatePickerEvent::Change` carries `DateTime` instead of `Date`: ```diff cx.subscribe(&picker, |this, _, event, _| match event { - DatePickerEvent::Change(date) => match date { + DatePickerEvent::Change(value) => match value.date() { Date::Single(Some(date)) => { /* … */ } _ => {} }, }); ``` `format` keeps its signature, so `value.format("%Y-%m-%d")` still works unchanged. `DateRangePresetValue` gains a `DateTime` variant, so exhaustive matches on it need a new arm. ## Test plan - [x] `TimeField` editing rules unit tests in `gpui-base` (24- and 12-hour typing, stepping, midnight/noon labels) - [x] `test_time_field_builder` in `gpui-component` - [x] Integration tests in `crates/kit/tests/date_picker.rs`: live changes while the popup stays open; 12-hour typing with `p`; range picker stays date-only and keeps owner-set times - [x] `cargo test` for base, component, kit date_picker, shell - [x] `cargo clippy --workspace --all-targets -- --deny warnings` - [ ] Check the 12-hour field layout in the story (`cargo run -- DatePicker`, "Date and time") - [ ] Check the `TimeField` story and the `time-field` base showcase (`cargo run -p gpui-base-examples -- time-field`); the WASM showcase build is left to CI --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> | 12 天前 | |
slider: Let a touch drag on the track move the thumb (#3313) ## Description Thank you for GPUI Kit, and for making it usable on mobile at all. While testing every Kit 0.7.0 component on Android (through gpui-mobile), I found two small problems with clear fixes, so I'm proposing them here. Each is a separate commit, and I'm happy to split them into separate PRs, change the approach, or drop either one if it doesn't fit how you want to handle touch. The remaining findings are collected in #3310. ### 1. Slider: a touch drag on the track moves the thumb On touch platforms (gpui-mobile on Android/iOS), a finger on the slider track could not move the thumb: GPUI delivers a finger drag as `TouchDragEvent` (when a handler claims it with `window.prevent_default()` on `Started`) or as scrolling, while `on_drag` drag-and-drop only reacts to the mouse. Dragging a slider scrolled the page instead. `SliderTrack` now adds an absolutely positioned `canvas` layer (only when not disabled) that claims touch drags starting on the track and feeds them through the existing `update_value_by_position` / `handle_release` path, so `SliderEvent::Change` and `SliderEvent::Release` fire as for the mouse. For range sliders the thumb nearer to the touch start moves. Drags already claimed by another handler are left alone, and mouse behaviour is unchanged. ### 2. fps: use Droid Sans Mono on Android On Android, the first render of `FpsMonitor` panics: `DEFAULT_FONT` falls through to `"monospace"`, which Android's cosmic-text backend does not resolve as an alias. None of GPUI's desktop fallbacks exist there either, so font resolution fails. iOS already has its own branch for the same reason. This adds an Android branch that uses `Droid Sans Mono`, a family every Android system image ships (`/system/fonts/DroidSansMono.ttf`). ## Screenshot Slider: behavioural change, no visual difference. fps: before, the app aborts with `failed to resolve font 'monospace' or any of the fallbacks` when the HUD first paints; after, the HUD renders. ## How to Test - New test `slider_touch_drag_moves_the_actual_thumb` in `crates/kit/tests/disclosure.rs` dispatches `TouchDragEvent` Started/Moved/Ended and checks the thumb follows in both directions. It fails without the change (`assertion failed: after.center().x > before.center().x`) and passes with it. - `disabled_slider_ignores_pointer_changes` now also checks that a touch drag leaves a disabled slider alone. - `cargo test -p gpui-kit --features test-support,component --test disclosure`: 6 passed. - On device: the same change, as a patch on Kit 0.7.0, in an Android test app (gpui-mobile host path, Android 13 emulator via Redroid). Horizontal, vertical and range sliders follow the finger. - `cargo check -p gpui-fps` and `cargo fmt --check` pass on Linux (non-Android targets compile the same code as before). - FPS HUD on device: the same Android test app renders the HUD instead of panicking. ## Checklist - [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and followed the guidelines. - [x] Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate. - [ ] Passed `cargo run` for story tests related to the changes. (Not run; the slider is covered by the test above, the fps change is an Android-only `cfg`.) - [x] Tested macOS, Windows and Linux platforms performance (if the change is platform-specific) — desktop mouse path and desktop font constants unchanged; touch and Android paths tested on Android. Playground used for the on-device testing: [Bombatomica64/Gpui-android](https://github.com/Bombatomica64/Gpui-android) ([prebuilt APK](https://github.com/Bombatomica64/Gpui-android/releases/tag/v0.1.1)), in case you want to try these changes on Android. Thanks for taking the time to review this. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Jason Lee <huacnlee@gmail.com> | 7 天前 | |
root: Add Base window hosting and a single Kit startup entry point (#3152) ## Summary Window startup now always mounts a `gpui_base::Root`, regardless of Cargo feature unification. Base owns application content, overlay hosting, keyboard traversal, and text-selection copying. Component initialization registers its per-window presentation state as a Root plugin, so dialogs, sheets, notifications, menus, tooltips, touch selection, and window chrome remain automatic without making Base depend on Component. - Add `gpui_kit::open_window` as the standard Kit application entry point. It always mounts Base Root and returns both the window handle and content entity. - Move Root ownership and unconditional overlay hosting into `gpui-base`; `gpui_component::Root` is now a re-export. Root now renders sheet, dialog, and notification layers automatically. - Add the typed `RootPlugin` interface. Plugins are registered before window creation, instantiated independently per window, rendered in registration order, and can prepare, style, and decorate the root surface. - Keep Component presentation in a `WindowState` Root plugin while application-facing operations remain on `WindowExt`. - Make `window_border()` solely responsible for client-side window chrome. Server-decorated windows pass content through unchanged; client-decorated windows receive borders, shadow inset, rounded corners, and resize hit zones. - Remove `Root::bordered` and `Root::window_shadow_size`, plus the obsolete `root_borderless` example. - Migrate Kit examples, stories, tests, documentation, and pending 0.7.0 release notes to the new startup and Root APIs. ## Public API ### gpui-base ```rust pub trait RootPlugin: Render + Sized { fn prepare(&mut self, window: &mut Window, cx: &mut Context<Self>) {} fn style(&self, surface: &mut Stateful<Div>, window: &mut Window, cx: &mut App) {} fn decorate( &self, surface: AnyElement, root: &Root, window: &mut Window, cx: &mut App, ) -> impl IntoElement { surface } } impl Root { pub fn new( view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>, ) -> Self; pub fn register_plugin<V: RootPlugin>( cx: &mut App, build: fn(&mut Window, &mut Context<V>) -> V, ); pub fn plugin<V: RootPlugin>(&self) -> Option<Entity<V>>; pub fn view(&self) -> &AnyView; pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self; pub fn update<R>( window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R, ) -> R; } ``` `Root` also implements `Styled`. Instance style refinements are applied after plugin defaults and therefore take precedence: ```rust impl Styled for Root { fn style(&mut self) -> &mut StyleRefinement; } ``` Register plugins during explicit application initialization, before creating windows. Re-registering a plugin type replaces its factory for future windows rather than mounting it twice. Registration does not retrofit existing roots. ### gpui-kit ```rust pub fn open_window<V: Render>( options: WindowOptions, cx: &mut App, build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, ) -> Result<(AnyWindowHandle, Entity<V>)>; ``` The builder returns application content, not a Root. Kit mounts Base Root around it. ### gpui-component `pub use gpui_base::Root;` replaces the former Component-owned Root. `gpui_component::init(cx)` registers Component `WindowState` as a Root plugin. Manual layer-rendering and Root-owned dialog, sheet, and notification operations are removed. In particular, `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` no longer exist because Root hosts those layers automatically. Use the existing `WindowExt` operations to open and update them. ## Breaking Changes Targeted for 0.7.0; package versions remain unchanged. Use the Kit window entry point and return application content instead of constructing `Root` manually. Retain the returned content entity when direct content access is needed, because the window root is now `gpui_base::Root`: ```diff - cx.open_window(options, |window, cx| { - let content = build_content(window, cx); - cx.new(|cx| Root::new(content, window, cx)) - }) + let (window_handle, content) = + gpui_kit::open_window(options, cx, |window, cx| { + build_content(window, cx) + })?; ``` Delete manual overlay placement. `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` are removed because `Root` now hosts these layers automatically: ```diff - let sheet_layer = Root::render_sheet_layer(window, cx); - let dialog_layer = Root::render_dialog_layer(window, cx); - let notification_layer = Root::render_notification_layer(window, cx); - div() .child(content) - .children(sheet_layer) - .children(dialog_layer.map(|layer| deferred(layer).with_priority(1))) - .children(notification_layer) ``` Root window-chrome configuration is removed. Decoration policy comes from GPUI `WindowOptions`; `window_border()` applies client chrome only for `Decorations::Client`: ```diff - Root::bordered - Root::window_shadow_size + window_border() ``` The Root- and WindowExt-owned text-selection methods are removed in favor of `gpui_base::TextSelection`: ```diff - window.selected_text(cx) + TextSelection::selected_text(window, cx) - window.has_text_selection(cx) + TextSelection::has_selection(window, cx) - root.clear_text_selection(window, cx) - window.clear_text_selection(cx) + TextSelection::clear(window, cx) - window.end_text_selection(cx) + TextSelection::end(window, cx) ``` The remaining Component-owned Root operations are removed in favor of the corresponding `WindowExt` methods: ```diff - root.open_dialog(build, window, cx) + window.open_dialog(cx, build) - root.close_dialog(window, cx) + window.close_dialog(cx) - root.close_all_dialogs(window, cx) + window.close_all_dialogs(cx) - root.open_sheet_at(placement, build, window, cx) + window.open_sheet_at(placement, cx, build) - root.close_sheet(window, cx) + window.close_sheet(cx) - root.push_notification(notification, window, cx) + window.push_notification(notification, cx) - root.remove_notification::<T>(window, cx) + window.remove_notification::<T>(cx) - root.remove_notification1::<T>(key, window, cx) + window.remove_notification1::<T>(key, cx) - root.clear_notifications(window, cx) + window.clear_notifications(cx) ``` ## Test Plan - `cargo test -p gpui-base --lib` — 993 passed. - `cargo test -p gpui-component --lib` — 559 passed. - `cargo test -p gpui-kit --features test-support,component,assets --test root` — 6 passed. - `cargo test -p gpui-kit --features test-support,component,assets --tests` — 114 passed during the window-startup migration. - `cargo test -p gpui-kit --features test-support,component,assets --test rendering` — 2 Metal pixel checks passed. - `cargo clippy -p gpui-base -p gpui-component -p gpui-kit --all-targets --features gpui-kit/test-support -- --deny warnings` — passed. - `cargo check -p gpui-kit --no-default-features` — passed. - `cargo fmt --all --check` — passed. - `git diff --check` — passed. - `script/check-ai-recipes` — 9 published recipe fragments passed. AI-assisted changes prepared with Codex. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> | 14 天前 | |
chore: Forward platform test support and profiling (#2966) I use GPUI Kit in NearWord (app based on gpui-kit now). These changes come from my fork, that I made during testing/ `test-support` enables GPUI's test harness but leaves out the native platform implementation. On macOS, screenshot capture can fail with `render_to_image not implemented for this platform`. This patch also enables `gpui_platform/test-support`. It adds `profiler = ["gpui/profiler"]` so applications can enable profiling through `gpui-kit`, without adding a direct GPUI dependency. Profiling stays opt-in. The patch includes feature documentation and a compile-time `WindowProfiler` export check. Default features, dependency versions, and the lockfile are unchanged. ## How to Test Passed on macOS: - Build checks with default features, no default features, and `test-support` and `profiler` separately and together. - Kit tests, doctests, formatting, and Clippy with warnings denied. - Isolated consumer checks for platform test support and opt-in profiling. - Native Metal capture, with the center pixel matching `[51, 102, 153, 255]`. <details> <summary>Build and test commands</summary> Run from the repository root. All commands should pass. The export check runs at compile time. ```sh cargo check -p gpui-kit --locked cargo check -p gpui-kit --no-default-features --locked cargo check -p gpui-kit --features test-support --locked cargo check -p gpui-kit --features profiler --locked cargo check -p gpui-kit --features test-support,profiler --locked cargo test -p gpui-kit --features test-support,profiler --locked cargo clippy -p gpui-kit --all-targets --features test-support,profiler --locked -- -D warnings ``` </details> <details> <summary>Reproduce native screenshot capture on macOS</summary> On macOS with Metal, save this as `crates/kit/examples/native_render.rs`; create the directory if needed. This temporary probe is not part of the committed tests. ```rust use gpui_kit::{AppContext as _, Context, IntoElement, Render, Styled, VisualTestAppContext, div, px, size}; struct Swatch; impl Render for Swatch { fn render(&mut self, _: &mut gpui_kit::Window, _: &mut Context<Self>) -> impl IntoElement { div().size_full().bg(gpui_kit::rgb(0x336699)) } } fn main() { let mut cx = VisualTestAppContext::new(gpui_kit::platform::current_platform(false)); let window = cx.open_offscreen_window(size(px(64.), px(48.)), |_, cx| cx.new(|_| Swatch)) .expect("open native offscreen window"); cx.run_until_parked(); let image = cx.capture_screenshot(window.into()).expect("native render_to_image"); let pixel = image.get_pixel(image.width()/2, image.height()/2); assert_eq!(pixel.0, [0x33, 0x66, 0x99, 0xff]); println!("native rendering passed: {}x{}, center={:?}", image.width(), image.height(), pixel.0); } ``` From the repository root, run: ```sh cargo run -p gpui-kit --example native_render --features test-support --locked ``` Capture and the pixel assertion should succeed. Observed output: ```text native rendering passed: 128x96, center=[51, 102, 153, 255] ``` Image dimensions depend on the display scale; the center pixel must match. Remove the temporary example after testing: ```sh rm crates/kit/examples/native_render.rs ``` </details> Story was not used for this feature-flag change; the native probe checks screenshot capture directly. Windows and Linux runtime checks were not run. ## Checklist - [x] Read [CONTRIBUTING.md](https://github.com/longbridge/gpui-kit/blob/main/CONTRIBUTING.md). - [x] Reviewed the patch, including AI-assisted changes. - [x] Manually tested the patch. - [ ] Windows and Linux runtime validation. ## AI assistance OpenAI’s `gpt-6-astra` extracted these changes from my GPUI Kit fork used in NearWord and prepared this PR. I reviewed the patch and made edits before submitting it. --------- Co-authored-by: Jason Lee <huacnlee@gmail.com> | 1 个月前 | |
kit: Add optional gpui-fast backend (#3375) Add an optional `gpui-fast` feature across GPUI Kit’s existing crates: ```toml gpui-kit = { version = "0.7", features = ["gpui-fast"] } ``` No new crates. Preserve the existing `gpui-pre-*` workspace dependencies and default backend; optional Fast dependencies and internal conditional aliases select the engine. Existing application imports and macros continue to work. This is additive, with no breaking changes. Upstream packages still compile because Cargo features are additive. ## Public API - **gpui-kit:** `gpui-fast` selects Fast for core, platforms, and enabled layers. - **gpui-base, gpui-component, gpui-kit-assets, gpui-fps, gpui-wry, gpui-shell, gpui-component-shell:** `gpui-fast` selects the matching backend for standalone consumers. - **gpui-component-macros:** `#[derive(IntoPlot)]` additionally recognizes direct Fast dependencies. - Gallery and examples expose the same feature for comparisons. ## Validation 27 focused UI/macro/assets tests and 9 documentation tests pass. Default/Fast configurations, examples, WebView, Shell, benchmarks, and nightly WebAssembly compile. Clippy, formatting, dependency, and snapshot-pin checks pass. CI adds native and WebAssembly coverage. AI-assisted with Codex. | 20 小时前 | |
input: Cover UI workflows and fix disabled focus and vertical selection (#3256) ## Summary Expand Input, Textarea and Editor interaction regressions from 82 to **159 UI cases**, with an operation matrix and a single local gate: `script/test-input`. The aim is to replace the routine manual editing tour with reproducible assertions and make this suite an example for future component UI tests. Coverage includes typing/replacement, character/word/line/document/page movement, forward/reverse selection, Unicode and CRLF, pointer/word/paragraph selection, drag autoscroll, clipboard, Undo/Redo, validation and masks, readonly/disabled transitions, focus/lifecycle, wrapping/resizing, pairs/indentation, folding, multi-cursor editing, search/replace, deterministic completion/code-action/inline/definition providers, and IME protocol transitions on all three controls. Assertions inspect text together with selections, caret geometry, focus, history or host callbacks as appropriate. ### Production fixes exposed by the tests - Preserve disabled Input pointer protection, document-edge selection, preferred columns after horizontal selection/Alt-click/offscreen navigation, and visual-row affinity through Shift-End followed by Down or Shift-Down. - Clamp drag selection to laid-out text while autoscroll reveals subsequent rows, instead of immediately selecting to EOF. - Cancel stale completion/inline requests on editing, Escape, blur, protection changes and popup acceptance. - Report the IME selection direction correctly and prevent canceled preedit from merging surrounding typing into one Undo transaction. - Preserve replacement-field focus when its mode change echoes through overlay synchronization. - Make the existing GoToDefinition action query its provider without requiring prior hover. Separate explicit navigation from hover cancellation; reject superseded responses and responses after text/caret/focus changes. No new default keybinding. The runtime changes are limited to these paths. No public API signatures change. The existing `press` helper correction pairs key-down/up and avoids simulated IME text injection for Enter/Tab; its regressions remain included. The branch starts from main after #3253 and does not depend on #3254's accessibility behavior. It also carries the isolated gallery-fixture CI correction shared with #3254: materialize registered surfaces in separate frames without changing production execution budgets. ## Verification (Linux) - `script/test-input`: **536 passed**, comprising Base **300**, Component **44**, Input UI **159**, focus **4**, keyboard helpers **16**, touch selection **13**; no failures or ignored cases in the selected runs. - All **17 Kit interaction targets: 270 passed** (includes the Input/focus/helper/touch cases above). - `cargo clippy -p gpui-kit --features test-support,assets --test input --test input_focus --test interactions --test touch_selection --locked -- --deny warnings`: passed. - Scoped rustfmt, `bash -n script/test-input`, and `git diff --check`: passed. - Five independent review/test scopes were used. Observed failing cases were corrected and rerun; tests retain explicit blur observation, distinct clipboard sentinels and controlled provider responses rather than incidental cancellation/reset steps. The existing CI matrix runs the relevant targets on Linux, macOS and Windows. Results for the new head must be checked before merging. ## Review gate and boundaries See [`crates/kit/tests/input/README.md`](crates/kit/tests/input/README.md) for the operation matrix, fixtures, exact commands and instructions for adding a failing regression for each changed interaction. For ordinary editing changes, use that regression plus the full gate and platform CI. This covers repeatable interaction contracts, not every possible document/configuration/event ordering. Composition tests call the public `ElementInputHandler` bridge for mounted controls; they do not drive a real OS input method or the platform-installed handler. Native IME/candidate windows, accessibility dispatch, clipboard/menu adapters and pixel rendering retain their relevant platform checks. The in-flight completion disposal fixture is inactive because TestPlatform retains the active window's input handler after removal; it does not certify active-window platform disposal. Unicode movement preserves the existing scalar-boundary contract; grapheme movement, regex search, duplicate/move-line commands and Shift-Page selection are not introduced by these tests. ## AI assistance Implementation, tests, documentation and review were assisted by OpenAI Codex. | 10 天前 | |
input: Cover UI workflows and fix disabled focus and vertical selection (#3256) ## Summary Expand Input, Textarea and Editor interaction regressions from 82 to **159 UI cases**, with an operation matrix and a single local gate: `script/test-input`. The aim is to replace the routine manual editing tour with reproducible assertions and make this suite an example for future component UI tests. Coverage includes typing/replacement, character/word/line/document/page movement, forward/reverse selection, Unicode and CRLF, pointer/word/paragraph selection, drag autoscroll, clipboard, Undo/Redo, validation and masks, readonly/disabled transitions, focus/lifecycle, wrapping/resizing, pairs/indentation, folding, multi-cursor editing, search/replace, deterministic completion/code-action/inline/definition providers, and IME protocol transitions on all three controls. Assertions inspect text together with selections, caret geometry, focus, history or host callbacks as appropriate. ### Production fixes exposed by the tests - Preserve disabled Input pointer protection, document-edge selection, preferred columns after horizontal selection/Alt-click/offscreen navigation, and visual-row affinity through Shift-End followed by Down or Shift-Down. - Clamp drag selection to laid-out text while autoscroll reveals subsequent rows, instead of immediately selecting to EOF. - Cancel stale completion/inline requests on editing, Escape, blur, protection changes and popup acceptance. - Report the IME selection direction correctly and prevent canceled preedit from merging surrounding typing into one Undo transaction. - Preserve replacement-field focus when its mode change echoes through overlay synchronization. - Make the existing GoToDefinition action query its provider without requiring prior hover. Separate explicit navigation from hover cancellation; reject superseded responses and responses after text/caret/focus changes. No new default keybinding. The runtime changes are limited to these paths. No public API signatures change. The existing `press` helper correction pairs key-down/up and avoids simulated IME text injection for Enter/Tab; its regressions remain included. The branch starts from main after #3253 and does not depend on #3254's accessibility behavior. It also carries the isolated gallery-fixture CI correction shared with #3254: materialize registered surfaces in separate frames without changing production execution budgets. ## Verification (Linux) - `script/test-input`: **536 passed**, comprising Base **300**, Component **44**, Input UI **159**, focus **4**, keyboard helpers **16**, touch selection **13**; no failures or ignored cases in the selected runs. - All **17 Kit interaction targets: 270 passed** (includes the Input/focus/helper/touch cases above). - `cargo clippy -p gpui-kit --features test-support,assets --test input --test input_focus --test interactions --test touch_selection --locked -- --deny warnings`: passed. - Scoped rustfmt, `bash -n script/test-input`, and `git diff --check`: passed. - Five independent review/test scopes were used. Observed failing cases were corrected and rerun; tests retain explicit blur observation, distinct clipboard sentinels and controlled provider responses rather than incidental cancellation/reset steps. The existing CI matrix runs the relevant targets on Linux, macOS and Windows. Results for the new head must be checked before merging. ## Review gate and boundaries See [`crates/kit/tests/input/README.md`](crates/kit/tests/input/README.md) for the operation matrix, fixtures, exact commands and instructions for adding a failing regression for each changed interaction. For ordinary editing changes, use that regression plus the full gate and platform CI. This covers repeatable interaction contracts, not every possible document/configuration/event ordering. Composition tests call the public `ElementInputHandler` bridge for mounted controls; they do not drive a real OS input method or the platform-installed handler. Native IME/candidate windows, accessibility dispatch, clipboard/menu adapters and pixel rendering retain their relevant platform checks. The in-flight completion disposal fixture is inactive because TestPlatform retains the active window's input handler after removal; it does not certify active-window platform disposal. Unicode movement preserves the existing scalar-boundary contract; grapheme movement, regex search, duplicate/move-line commands and Shift-Page selection are not introduced by these tests. ## AI assistance Implementation, tests, documentation and review were assisted by OpenAI Codex. | 10 天前 | |
kit: Add optional gpui-fast backend (#3375) Add an optional `gpui-fast` feature across GPUI Kit’s existing crates: ```toml gpui-kit = { version = "0.7", features = ["gpui-fast"] } ``` No new crates. Preserve the existing `gpui-pre-*` workspace dependencies and default backend; optional Fast dependencies and internal conditional aliases select the engine. Existing application imports and macros continue to work. This is additive, with no breaking changes. Upstream packages still compile because Cargo features are additive. ## Public API - **gpui-kit:** `gpui-fast` selects Fast for core, platforms, and enabled layers. - **gpui-base, gpui-component, gpui-kit-assets, gpui-fps, gpui-wry, gpui-shell, gpui-component-shell:** `gpui-fast` selects the matching backend for standalone consumers. - **gpui-component-macros:** `#[derive(IntoPlot)]` additionally recognizes direct Fast dependencies. - Gallery and examples expose the same feature for comparisons. ## Validation 27 focused UI/macro/assets tests and 9 documentation tests pass. Default/Fast configurations, examples, WebView, Shell, benchmarks, and nightly WebAssembly compile. Clippy, formatting, dependency, and snapshot-pin checks pass. CI adds native and WebAssembly coverage. AI-assisted with Codex. | 20 小时前 | |
root: Add Base window hosting and a single Kit startup entry point (#3152) ## Summary Window startup now always mounts a `gpui_base::Root`, regardless of Cargo feature unification. Base owns application content, overlay hosting, keyboard traversal, and text-selection copying. Component initialization registers its per-window presentation state as a Root plugin, so dialogs, sheets, notifications, menus, tooltips, touch selection, and window chrome remain automatic without making Base depend on Component. - Add `gpui_kit::open_window` as the standard Kit application entry point. It always mounts Base Root and returns both the window handle and content entity. - Move Root ownership and unconditional overlay hosting into `gpui-base`; `gpui_component::Root` is now a re-export. Root now renders sheet, dialog, and notification layers automatically. - Add the typed `RootPlugin` interface. Plugins are registered before window creation, instantiated independently per window, rendered in registration order, and can prepare, style, and decorate the root surface. - Keep Component presentation in a `WindowState` Root plugin while application-facing operations remain on `WindowExt`. - Make `window_border()` solely responsible for client-side window chrome. Server-decorated windows pass content through unchanged; client-decorated windows receive borders, shadow inset, rounded corners, and resize hit zones. - Remove `Root::bordered` and `Root::window_shadow_size`, plus the obsolete `root_borderless` example. - Migrate Kit examples, stories, tests, documentation, and pending 0.7.0 release notes to the new startup and Root APIs. ## Public API ### gpui-base ```rust pub trait RootPlugin: Render + Sized { fn prepare(&mut self, window: &mut Window, cx: &mut Context<Self>) {} fn style(&self, surface: &mut Stateful<Div>, window: &mut Window, cx: &mut App) {} fn decorate( &self, surface: AnyElement, root: &Root, window: &mut Window, cx: &mut App, ) -> impl IntoElement { surface } } impl Root { pub fn new( view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>, ) -> Self; pub fn register_plugin<V: RootPlugin>( cx: &mut App, build: fn(&mut Window, &mut Context<V>) -> V, ); pub fn plugin<V: RootPlugin>(&self) -> Option<Entity<V>>; pub fn view(&self) -> &AnyView; pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self; pub fn update<R>( window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R, ) -> R; } ``` `Root` also implements `Styled`. Instance style refinements are applied after plugin defaults and therefore take precedence: ```rust impl Styled for Root { fn style(&mut self) -> &mut StyleRefinement; } ``` Register plugins during explicit application initialization, before creating windows. Re-registering a plugin type replaces its factory for future windows rather than mounting it twice. Registration does not retrofit existing roots. ### gpui-kit ```rust pub fn open_window<V: Render>( options: WindowOptions, cx: &mut App, build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, ) -> Result<(AnyWindowHandle, Entity<V>)>; ``` The builder returns application content, not a Root. Kit mounts Base Root around it. ### gpui-component `pub use gpui_base::Root;` replaces the former Component-owned Root. `gpui_component::init(cx)` registers Component `WindowState` as a Root plugin. Manual layer-rendering and Root-owned dialog, sheet, and notification operations are removed. In particular, `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` no longer exist because Root hosts those layers automatically. Use the existing `WindowExt` operations to open and update them. ## Breaking Changes Targeted for 0.7.0; package versions remain unchanged. Use the Kit window entry point and return application content instead of constructing `Root` manually. Retain the returned content entity when direct content access is needed, because the window root is now `gpui_base::Root`: ```diff - cx.open_window(options, |window, cx| { - let content = build_content(window, cx); - cx.new(|cx| Root::new(content, window, cx)) - }) + let (window_handle, content) = + gpui_kit::open_window(options, cx, |window, cx| { + build_content(window, cx) + })?; ``` Delete manual overlay placement. `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` are removed because `Root` now hosts these layers automatically: ```diff - let sheet_layer = Root::render_sheet_layer(window, cx); - let dialog_layer = Root::render_dialog_layer(window, cx); - let notification_layer = Root::render_notification_layer(window, cx); - div() .child(content) - .children(sheet_layer) - .children(dialog_layer.map(|layer| deferred(layer).with_priority(1))) - .children(notification_layer) ``` Root window-chrome configuration is removed. Decoration policy comes from GPUI `WindowOptions`; `window_border()` applies client chrome only for `Decorations::Client`: ```diff - Root::bordered - Root::window_shadow_size + window_border() ``` The Root- and WindowExt-owned text-selection methods are removed in favor of `gpui_base::TextSelection`: ```diff - window.selected_text(cx) + TextSelection::selected_text(window, cx) - window.has_text_selection(cx) + TextSelection::has_selection(window, cx) - root.clear_text_selection(window, cx) - window.clear_text_selection(cx) + TextSelection::clear(window, cx) - window.end_text_selection(cx) + TextSelection::end(window, cx) ``` The remaining Component-owned Root operations are removed in favor of the corresponding `WindowExt` methods: ```diff - root.open_dialog(build, window, cx) + window.open_dialog(cx, build) - root.close_dialog(window, cx) + window.close_dialog(cx) - root.close_all_dialogs(window, cx) + window.close_all_dialogs(cx) - root.open_sheet_at(placement, build, window, cx) + window.open_sheet_at(placement, cx, build) - root.close_sheet(window, cx) + window.close_sheet(cx) - root.push_notification(notification, window, cx) + window.push_notification(notification, cx) - root.remove_notification::<T>(window, cx) + window.remove_notification::<T>(cx) - root.remove_notification1::<T>(key, window, cx) + window.remove_notification1::<T>(key, cx) - root.clear_notifications(window, cx) + window.clear_notifications(cx) ``` ## Test Plan - `cargo test -p gpui-base --lib` — 993 passed. - `cargo test -p gpui-component --lib` — 559 passed. - `cargo test -p gpui-kit --features test-support,component,assets --test root` — 6 passed. - `cargo test -p gpui-kit --features test-support,component,assets --tests` — 114 passed during the window-startup migration. - `cargo test -p gpui-kit --features test-support,component,assets --test rendering` — 2 Metal pixel checks passed. - `cargo clippy -p gpui-base -p gpui-component -p gpui-kit --all-targets --features gpui-kit/test-support -- --deny warnings` — passed. - `cargo check -p gpui-kit --no-default-features` — passed. - `cargo fmt --all --check` — passed. - `git diff --check` — passed. - `script/check-ai-recipes` — 9 published recipe fragments passed. AI-assisted changes prepared with Codex. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> | 14 天前 | |
root: Add Base window hosting and a single Kit startup entry point (#3152) ## Summary Window startup now always mounts a `gpui_base::Root`, regardless of Cargo feature unification. Base owns application content, overlay hosting, keyboard traversal, and text-selection copying. Component initialization registers its per-window presentation state as a Root plugin, so dialogs, sheets, notifications, menus, tooltips, touch selection, and window chrome remain automatic without making Base depend on Component. - Add `gpui_kit::open_window` as the standard Kit application entry point. It always mounts Base Root and returns both the window handle and content entity. - Move Root ownership and unconditional overlay hosting into `gpui-base`; `gpui_component::Root` is now a re-export. Root now renders sheet, dialog, and notification layers automatically. - Add the typed `RootPlugin` interface. Plugins are registered before window creation, instantiated independently per window, rendered in registration order, and can prepare, style, and decorate the root surface. - Keep Component presentation in a `WindowState` Root plugin while application-facing operations remain on `WindowExt`. - Make `window_border()` solely responsible for client-side window chrome. Server-decorated windows pass content through unchanged; client-decorated windows receive borders, shadow inset, rounded corners, and resize hit zones. - Remove `Root::bordered` and `Root::window_shadow_size`, plus the obsolete `root_borderless` example. - Migrate Kit examples, stories, tests, documentation, and pending 0.7.0 release notes to the new startup and Root APIs. ## Public API ### gpui-base ```rust pub trait RootPlugin: Render + Sized { fn prepare(&mut self, window: &mut Window, cx: &mut Context<Self>) {} fn style(&self, surface: &mut Stateful<Div>, window: &mut Window, cx: &mut App) {} fn decorate( &self, surface: AnyElement, root: &Root, window: &mut Window, cx: &mut App, ) -> impl IntoElement { surface } } impl Root { pub fn new( view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>, ) -> Self; pub fn register_plugin<V: RootPlugin>( cx: &mut App, build: fn(&mut Window, &mut Context<V>) -> V, ); pub fn plugin<V: RootPlugin>(&self) -> Option<Entity<V>>; pub fn view(&self) -> &AnyView; pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self; pub fn update<R>( window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R, ) -> R; } ``` `Root` also implements `Styled`. Instance style refinements are applied after plugin defaults and therefore take precedence: ```rust impl Styled for Root { fn style(&mut self) -> &mut StyleRefinement; } ``` Register plugins during explicit application initialization, before creating windows. Re-registering a plugin type replaces its factory for future windows rather than mounting it twice. Registration does not retrofit existing roots. ### gpui-kit ```rust pub fn open_window<V: Render>( options: WindowOptions, cx: &mut App, build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, ) -> Result<(AnyWindowHandle, Entity<V>)>; ``` The builder returns application content, not a Root. Kit mounts Base Root around it. ### gpui-component `pub use gpui_base::Root;` replaces the former Component-owned Root. `gpui_component::init(cx)` registers Component `WindowState` as a Root plugin. Manual layer-rendering and Root-owned dialog, sheet, and notification operations are removed. In particular, `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` no longer exist because Root hosts those layers automatically. Use the existing `WindowExt` operations to open and update them. ## Breaking Changes Targeted for 0.7.0; package versions remain unchanged. Use the Kit window entry point and return application content instead of constructing `Root` manually. Retain the returned content entity when direct content access is needed, because the window root is now `gpui_base::Root`: ```diff - cx.open_window(options, |window, cx| { - let content = build_content(window, cx); - cx.new(|cx| Root::new(content, window, cx)) - }) + let (window_handle, content) = + gpui_kit::open_window(options, cx, |window, cx| { + build_content(window, cx) + })?; ``` Delete manual overlay placement. `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` are removed because `Root` now hosts these layers automatically: ```diff - let sheet_layer = Root::render_sheet_layer(window, cx); - let dialog_layer = Root::render_dialog_layer(window, cx); - let notification_layer = Root::render_notification_layer(window, cx); - div() .child(content) - .children(sheet_layer) - .children(dialog_layer.map(|layer| deferred(layer).with_priority(1))) - .children(notification_layer) ``` Root window-chrome configuration is removed. Decoration policy comes from GPUI `WindowOptions`; `window_border()` applies client chrome only for `Decorations::Client`: ```diff - Root::bordered - Root::window_shadow_size + window_border() ``` The Root- and WindowExt-owned text-selection methods are removed in favor of `gpui_base::TextSelection`: ```diff - window.selected_text(cx) + TextSelection::selected_text(window, cx) - window.has_text_selection(cx) + TextSelection::has_selection(window, cx) - root.clear_text_selection(window, cx) - window.clear_text_selection(cx) + TextSelection::clear(window, cx) - window.end_text_selection(cx) + TextSelection::end(window, cx) ``` The remaining Component-owned Root operations are removed in favor of the corresponding `WindowExt` methods: ```diff - root.open_dialog(build, window, cx) + window.open_dialog(cx, build) - root.close_dialog(window, cx) + window.close_dialog(cx) - root.close_all_dialogs(window, cx) + window.close_all_dialogs(cx) - root.open_sheet_at(placement, build, window, cx) + window.open_sheet_at(placement, cx, build) - root.close_sheet(window, cx) + window.close_sheet(cx) - root.push_notification(notification, window, cx) + window.push_notification(notification, cx) - root.remove_notification::<T>(window, cx) + window.remove_notification::<T>(cx) - root.remove_notification1::<T>(key, window, cx) + window.remove_notification1::<T>(key, cx) - root.clear_notifications(window, cx) + window.clear_notifications(cx) ``` ## Test Plan - `cargo test -p gpui-base --lib` — 993 passed. - `cargo test -p gpui-component --lib` — 559 passed. - `cargo test -p gpui-kit --features test-support,component,assets --test root` — 6 passed. - `cargo test -p gpui-kit --features test-support,component,assets --tests` — 114 passed during the window-startup migration. - `cargo test -p gpui-kit --features test-support,component,assets --test rendering` — 2 Metal pixel checks passed. - `cargo clippy -p gpui-base -p gpui-component -p gpui-kit --all-targets --features gpui-kit/test-support -- --deny warnings` — passed. - `cargo check -p gpui-kit --no-default-features` — passed. - `cargo fmt --all --check` — passed. - `git diff --check` — passed. - `script/check-ai-recipes` — 9 published recipe fragments passed. AI-assisted changes prepared with Codex. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> | 14 天前 | |
table, menu: Show keyboard focus on `DataTable` and keep menu highlights on key presses (#3307) Follow-up to #3299 / #3300, which noted that List / ListItem, Table and the menus are focusable or keyboard-navigable but don't go through `focus_style`. This PR checks each one and fixes the two that were actually broken. ## Audit With `focus_ring = false` and `focus_ring = true` (same result in both unless stated): | Component | Tab stop | Arrow keys | Keyboard focus / navigation (before) | Mouse hover | Selection | Verdict | | --- | --- | --- | --- | --- | --- | --- | | **DataTable** (`TableState`) | **Yes** (`tab_stop(true)`) | Up/Down/Left/Right, Home/End, PgUp/PgDn, Tab moves cells | **Nothing** when tabbed in. Rows only show once a key selects one. | `table_hover` row bg, hidden while typing (GPUI) | `accent` / `table_active` row or cell | **Fixed**: focus ring on keyboard focus | | **PopupMenu / ContextMenu / DropdownMenu** items | No: the menu holds focus while open | Up/Down/Left/Right | The `accent` highlight is the cursor. **Bug 1:** hover item A, press `down` → A **and** B both lit. **Bug 2:** hover A, press a key with no binding → highlight goes out, next `down` restarts at the top. | Hovering moves the highlight, the native menu convention | same highlight | **Fixed** both | | **AppMenuBar** | Titles are ghost `Button`s, so Tab stops with the #3299 edge line | Left/Right between menus | Open title shows the ghost `open` bg, and the popup is a `PopupMenu` (above) | ghost hover | — | Covered by `Button` + the PopupMenu fix | | **List / ListItem** | No. `ListState`'s handle isn't a tab stop. A searchable list focuses its query `Input`. | Up/Down | Selected row (`list_active` / `accent`) is the cursor, and it looks different from hover | `list_hover`, hidden while typing (GPUI) | same as keyboard cursor | **No change needed**, covered by a new test | Not changed, worth a separate decision: a List's selected row looks the same whether or not the list has focus, and `ListState` isn't a Tab stop. Native table views grey out the selection when unfocused. Doing that would change selection styling, which is out of scope here. ## Changes - **`DataTable`**: calls `focus_ring_style` when its handle is focused **and** `window.last_input_was_keyboard()`. That is GPUI's `focus_visible` rule; it matters here because clicking a row also focuses the table. - Bordered (default): the border is tinted `ring`, plus the outer ring when `focus_ring = true`. - `bordered(false)`: the #3299 1px edge line. - No layout change. The line is appended after the table content, so rows don't paint over it. - **`MenuItemElement`**: `group_hover` on its own group → `hover`. - GPUI computes an element's style *before* it pushes the element's group hitbox. A `group_hover` on the element's own group therefore takes the `hover_state.group` fallback, which ignores keyboard modality, so the pointer's item stayed lit next to the keyboard cursor. - `hover` uses the hitbox, which GPUI already suppresses after a key press. Mouse-hover look is unchanged. - The now-unused `group_name` is removed. - **`PopupMenu`**: the `on_hover(false)` that clears the highlight is skipped when the last input was a key press. GPUI ends hover under a still pointer on any key press, so the highlight used to vanish and the next arrow key restarted from the top. A real pointer exit still clears it. ## Tests `crates/kit/tests/rendering.rs` (main thread, `harness = false`, Metal). `Capture` now takes its scale from the window, so it works for any window size. - `menu_highlight_is_the_keyboard_cursor`: exactly one highlighted row after each step. - Hover `Beta` → `[1]`. - `down` → `[2]`. Fails with `[1, 2]` when `group_hover` is restored. - Hover `Beta` again → `[1]`. - Unbound `x` → `[1]`. Fails with `[]` without the `on_hover` guard. - `down` → `[2]`. - `list_selection_is_the_keyboard_cursor`: - Hover row 1 → only row 1 is lit. - `down` → only row 0 is lit, and its fill differs from the hover fill. - Fails with `[]` when the selection isn't forwarded to the item. Nothing needed fixing for List, so this one locks existing behaviour. - `table_shows_keyboard_focus_only`, for `focus_ring = false` and `true`: - Tab from a button into the table → `ring` pixels on its top border. With `true`, the outer band changes too. - In a fresh window, hover then click a row → the table is focused, but the border and outer band are identical to the hovered capture. - Fails without the fix, and also fails when the keyboard gate is dropped. ``` cargo test -p gpui-kit --features test-support,component,assets --test rendering # 10 passed (Metal) cargo test -p gpui-kit --features test-support,component,assets # 278 passed cargo test -p gpui-component # 575 passed cargo clippy -p gpui-component -p gpui-kit --features gpui-kit/test-support,gpui-kit/component,gpui-kit/assets --all-targets -- -D warnings ``` Note: `last_input_was_keyboard` flips back when the mouse moves, so the table's ring disappears on mouse movement, the same as GPUI's own `focus_visible` style. Switching modality already triggers a single `window.refresh()` in GPUI, so this adds no idle redraws. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 8 天前 | |
root: Add Base window hosting and a single Kit startup entry point (#3152) ## Summary Window startup now always mounts a `gpui_base::Root`, regardless of Cargo feature unification. Base owns application content, overlay hosting, keyboard traversal, and text-selection copying. Component initialization registers its per-window presentation state as a Root plugin, so dialogs, sheets, notifications, menus, tooltips, touch selection, and window chrome remain automatic without making Base depend on Component. - Add `gpui_kit::open_window` as the standard Kit application entry point. It always mounts Base Root and returns both the window handle and content entity. - Move Root ownership and unconditional overlay hosting into `gpui-base`; `gpui_component::Root` is now a re-export. Root now renders sheet, dialog, and notification layers automatically. - Add the typed `RootPlugin` interface. Plugins are registered before window creation, instantiated independently per window, rendered in registration order, and can prepare, style, and decorate the root surface. - Keep Component presentation in a `WindowState` Root plugin while application-facing operations remain on `WindowExt`. - Make `window_border()` solely responsible for client-side window chrome. Server-decorated windows pass content through unchanged; client-decorated windows receive borders, shadow inset, rounded corners, and resize hit zones. - Remove `Root::bordered` and `Root::window_shadow_size`, plus the obsolete `root_borderless` example. - Migrate Kit examples, stories, tests, documentation, and pending 0.7.0 release notes to the new startup and Root APIs. ## Public API ### gpui-base ```rust pub trait RootPlugin: Render + Sized { fn prepare(&mut self, window: &mut Window, cx: &mut Context<Self>) {} fn style(&self, surface: &mut Stateful<Div>, window: &mut Window, cx: &mut App) {} fn decorate( &self, surface: AnyElement, root: &Root, window: &mut Window, cx: &mut App, ) -> impl IntoElement { surface } } impl Root { pub fn new( view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>, ) -> Self; pub fn register_plugin<V: RootPlugin>( cx: &mut App, build: fn(&mut Window, &mut Context<V>) -> V, ); pub fn plugin<V: RootPlugin>(&self) -> Option<Entity<V>>; pub fn view(&self) -> &AnyView; pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self; pub fn update<R>( window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R, ) -> R; } ``` `Root` also implements `Styled`. Instance style refinements are applied after plugin defaults and therefore take precedence: ```rust impl Styled for Root { fn style(&mut self) -> &mut StyleRefinement; } ``` Register plugins during explicit application initialization, before creating windows. Re-registering a plugin type replaces its factory for future windows rather than mounting it twice. Registration does not retrofit existing roots. ### gpui-kit ```rust pub fn open_window<V: Render>( options: WindowOptions, cx: &mut App, build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, ) -> Result<(AnyWindowHandle, Entity<V>)>; ``` The builder returns application content, not a Root. Kit mounts Base Root around it. ### gpui-component `pub use gpui_base::Root;` replaces the former Component-owned Root. `gpui_component::init(cx)` registers Component `WindowState` as a Root plugin. Manual layer-rendering and Root-owned dialog, sheet, and notification operations are removed. In particular, `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` no longer exist because Root hosts those layers automatically. Use the existing `WindowExt` operations to open and update them. ## Breaking Changes Targeted for 0.7.0; package versions remain unchanged. Use the Kit window entry point and return application content instead of constructing `Root` manually. Retain the returned content entity when direct content access is needed, because the window root is now `gpui_base::Root`: ```diff - cx.open_window(options, |window, cx| { - let content = build_content(window, cx); - cx.new(|cx| Root::new(content, window, cx)) - }) + let (window_handle, content) = + gpui_kit::open_window(options, cx, |window, cx| { + build_content(window, cx) + })?; ``` Delete manual overlay placement. `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` are removed because `Root` now hosts these layers automatically: ```diff - let sheet_layer = Root::render_sheet_layer(window, cx); - let dialog_layer = Root::render_dialog_layer(window, cx); - let notification_layer = Root::render_notification_layer(window, cx); - div() .child(content) - .children(sheet_layer) - .children(dialog_layer.map(|layer| deferred(layer).with_priority(1))) - .children(notification_layer) ``` Root window-chrome configuration is removed. Decoration policy comes from GPUI `WindowOptions`; `window_border()` applies client chrome only for `Decorations::Client`: ```diff - Root::bordered - Root::window_shadow_size + window_border() ``` The Root- and WindowExt-owned text-selection methods are removed in favor of `gpui_base::TextSelection`: ```diff - window.selected_text(cx) + TextSelection::selected_text(window, cx) - window.has_text_selection(cx) + TextSelection::has_selection(window, cx) - root.clear_text_selection(window, cx) - window.clear_text_selection(cx) + TextSelection::clear(window, cx) - window.end_text_selection(cx) + TextSelection::end(window, cx) ``` The remaining Component-owned Root operations are removed in favor of the corresponding `WindowExt` methods: ```diff - root.open_dialog(build, window, cx) + window.open_dialog(cx, build) - root.close_dialog(window, cx) + window.close_dialog(cx) - root.close_all_dialogs(window, cx) + window.close_all_dialogs(cx) - root.open_sheet_at(placement, build, window, cx) + window.open_sheet_at(placement, cx, build) - root.close_sheet(window, cx) + window.close_sheet(cx) - root.push_notification(notification, window, cx) + window.push_notification(notification, cx) - root.remove_notification::<T>(window, cx) + window.remove_notification::<T>(cx) - root.remove_notification1::<T>(key, window, cx) + window.remove_notification1::<T>(key, cx) - root.clear_notifications(window, cx) + window.clear_notifications(cx) ``` ## Test Plan - `cargo test -p gpui-base --lib` — 993 passed. - `cargo test -p gpui-component --lib` — 559 passed. - `cargo test -p gpui-kit --features test-support,component,assets --test root` — 6 passed. - `cargo test -p gpui-kit --features test-support,component,assets --tests` — 114 passed during the window-startup migration. - `cargo test -p gpui-kit --features test-support,component,assets --test rendering` — 2 Metal pixel checks passed. - `cargo clippy -p gpui-base -p gpui-component -p gpui-kit --all-targets --features gpui-kit/test-support -- --deny warnings` — passed. - `cargo check -p gpui-kit --no-default-features` — passed. - `cargo fmt --all --check` — passed. - `git diff --check` — passed. - `script/check-ai-recipes` — 9 published recipe fragments passed. AI-assisted changes prepared with Codex. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> | 14 天前 | |
root: Add Base window hosting and a single Kit startup entry point (#3152) ## Summary Window startup now always mounts a `gpui_base::Root`, regardless of Cargo feature unification. Base owns application content, overlay hosting, keyboard traversal, and text-selection copying. Component initialization registers its per-window presentation state as a Root plugin, so dialogs, sheets, notifications, menus, tooltips, touch selection, and window chrome remain automatic without making Base depend on Component. - Add `gpui_kit::open_window` as the standard Kit application entry point. It always mounts Base Root and returns both the window handle and content entity. - Move Root ownership and unconditional overlay hosting into `gpui-base`; `gpui_component::Root` is now a re-export. Root now renders sheet, dialog, and notification layers automatically. - Add the typed `RootPlugin` interface. Plugins are registered before window creation, instantiated independently per window, rendered in registration order, and can prepare, style, and decorate the root surface. - Keep Component presentation in a `WindowState` Root plugin while application-facing operations remain on `WindowExt`. - Make `window_border()` solely responsible for client-side window chrome. Server-decorated windows pass content through unchanged; client-decorated windows receive borders, shadow inset, rounded corners, and resize hit zones. - Remove `Root::bordered` and `Root::window_shadow_size`, plus the obsolete `root_borderless` example. - Migrate Kit examples, stories, tests, documentation, and pending 0.7.0 release notes to the new startup and Root APIs. ## Public API ### gpui-base ```rust pub trait RootPlugin: Render + Sized { fn prepare(&mut self, window: &mut Window, cx: &mut Context<Self>) {} fn style(&self, surface: &mut Stateful<Div>, window: &mut Window, cx: &mut App) {} fn decorate( &self, surface: AnyElement, root: &Root, window: &mut Window, cx: &mut App, ) -> impl IntoElement { surface } } impl Root { pub fn new( view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>, ) -> Self; pub fn register_plugin<V: RootPlugin>( cx: &mut App, build: fn(&mut Window, &mut Context<V>) -> V, ); pub fn plugin<V: RootPlugin>(&self) -> Option<Entity<V>>; pub fn view(&self) -> &AnyView; pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self; pub fn update<R>( window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R, ) -> R; } ``` `Root` also implements `Styled`. Instance style refinements are applied after plugin defaults and therefore take precedence: ```rust impl Styled for Root { fn style(&mut self) -> &mut StyleRefinement; } ``` Register plugins during explicit application initialization, before creating windows. Re-registering a plugin type replaces its factory for future windows rather than mounting it twice. Registration does not retrofit existing roots. ### gpui-kit ```rust pub fn open_window<V: Render>( options: WindowOptions, cx: &mut App, build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, ) -> Result<(AnyWindowHandle, Entity<V>)>; ``` The builder returns application content, not a Root. Kit mounts Base Root around it. ### gpui-component `pub use gpui_base::Root;` replaces the former Component-owned Root. `gpui_component::init(cx)` registers Component `WindowState` as a Root plugin. Manual layer-rendering and Root-owned dialog, sheet, and notification operations are removed. In particular, `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` no longer exist because Root hosts those layers automatically. Use the existing `WindowExt` operations to open and update them. ## Breaking Changes Targeted for 0.7.0; package versions remain unchanged. Use the Kit window entry point and return application content instead of constructing `Root` manually. Retain the returned content entity when direct content access is needed, because the window root is now `gpui_base::Root`: ```diff - cx.open_window(options, |window, cx| { - let content = build_content(window, cx); - cx.new(|cx| Root::new(content, window, cx)) - }) + let (window_handle, content) = + gpui_kit::open_window(options, cx, |window, cx| { + build_content(window, cx) + })?; ``` Delete manual overlay placement. `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` are removed because `Root` now hosts these layers automatically: ```diff - let sheet_layer = Root::render_sheet_layer(window, cx); - let dialog_layer = Root::render_dialog_layer(window, cx); - let notification_layer = Root::render_notification_layer(window, cx); - div() .child(content) - .children(sheet_layer) - .children(dialog_layer.map(|layer| deferred(layer).with_priority(1))) - .children(notification_layer) ``` Root window-chrome configuration is removed. Decoration policy comes from GPUI `WindowOptions`; `window_border()` applies client chrome only for `Decorations::Client`: ```diff - Root::bordered - Root::window_shadow_size + window_border() ``` The Root- and WindowExt-owned text-selection methods are removed in favor of `gpui_base::TextSelection`: ```diff - window.selected_text(cx) + TextSelection::selected_text(window, cx) - window.has_text_selection(cx) + TextSelection::has_selection(window, cx) - root.clear_text_selection(window, cx) - window.clear_text_selection(cx) + TextSelection::clear(window, cx) - window.end_text_selection(cx) + TextSelection::end(window, cx) ``` The remaining Component-owned Root operations are removed in favor of the corresponding `WindowExt` methods: ```diff - root.open_dialog(build, window, cx) + window.open_dialog(cx, build) - root.close_dialog(window, cx) + window.close_dialog(cx) - root.close_all_dialogs(window, cx) + window.close_all_dialogs(cx) - root.open_sheet_at(placement, build, window, cx) + window.open_sheet_at(placement, cx, build) - root.close_sheet(window, cx) + window.close_sheet(cx) - root.push_notification(notification, window, cx) + window.push_notification(notification, cx) - root.remove_notification::<T>(window, cx) + window.remove_notification::<T>(cx) - root.remove_notification1::<T>(key, window, cx) + window.remove_notification1::<T>(key, cx) - root.clear_notifications(window, cx) + window.clear_notifications(cx) ``` ## Test Plan - `cargo test -p gpui-base --lib` — 993 passed. - `cargo test -p gpui-component --lib` — 559 passed. - `cargo test -p gpui-kit --features test-support,component,assets --test root` — 6 passed. - `cargo test -p gpui-kit --features test-support,component,assets --tests` — 114 passed during the window-startup migration. - `cargo test -p gpui-kit --features test-support,component,assets --test rendering` — 2 Metal pixel checks passed. - `cargo clippy -p gpui-base -p gpui-component -p gpui-kit --all-targets --features gpui-kit/test-support -- --deny warnings` — passed. - `cargo check -p gpui-kit --no-default-features` — passed. - `cargo fmt --all --check` — passed. - `git diff --check` — passed. - `script/check-ai-recipes` — 9 published recipe fragments passed. AI-assisted changes prepared with Codex. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> | 14 天前 | |
kit: expand core UI integration coverage (#3019) ## Description Add eight core UI integration workflows for Command and Combobox using the existing GPUI Kit testing API. Tests dispatch native input and assert filtering, selection, disabled behavior, callbacks, actions, clearing and focus restoration. The Escape regression catches duplicate Combobox Confirm events; the deferred cancellation handler now skips an already-closed popup. Keep all existing public exports and testing paths unchanged: `#[gpui_kit::test]` runs tests and `gpui_kit::test` provides UI interaction helpers. The independent Kit-only recipes package exercises the existing test macro and documented UI example. The 2026-09-08 facade decision is recorded in `crates/kit/src/lib.rs`: applications use GPUI Kit as their entry point and should not need to follow underlying GPUI crate migrations. Explain UI integration testing in API docs, English/Chinese guides and skill references. Restore HTML redirects from `/docs/ui-testing` to `/docs/test` in both languages, matching the existing legacy Markdown aliases and fixing the documentation CI failure. ## How to Test Verified locally on Linux: - `cargo test -p gpui-kit --features test-support --locked` — 82 tests and 2 doctests. - `cargo test --manifest-path examples/ai_recipes/Cargo.toml --locked --test gpui_test_contract --test settings --target-dir target` — 5 independent application tests. - `cargo clippy -p gpui-kit --tests --features test-support --locked -- -D warnings` - `bun run --cwd website build` - `bun run --cwd website test:seo` — 12 checks, including legacy testing-guide redirects. - Rustfmt and `git diff --check`. The Combobox fix also passed its 16 existing component tests. Native cross-platform results are reported by the PR checks. ## Checklist - [x] Read CONTRIBUTING and kept changes scoped to UI integration testing. - [x] Reviewed generated code and preserved existing public exports and testing paths. - [ ] Manual Story app interaction via `cargo run` (headless integration tests used locally). - [ ] Native cross-platform validation for the final revision (see CI checks). | 28 天前 | |
kit: Add optional gpui-fast backend (#3375) Add an optional `gpui-fast` feature across GPUI Kit’s existing crates: ```toml gpui-kit = { version = "0.7", features = ["gpui-fast"] } ``` No new crates. Preserve the existing `gpui-pre-*` workspace dependencies and default backend; optional Fast dependencies and internal conditional aliases select the engine. Existing application imports and macros continue to work. This is additive, with no breaking changes. Upstream packages still compile because Cargo features are additive. ## Public API - **gpui-kit:** `gpui-fast` selects Fast for core, platforms, and enabled layers. - **gpui-base, gpui-component, gpui-kit-assets, gpui-fps, gpui-wry, gpui-shell, gpui-component-shell:** `gpui-fast` selects the matching backend for standalone consumers. - **gpui-component-macros:** `#[derive(IntoPlot)]` additionally recognizes direct Fast dependencies. - Gallery and examples expose the same feature for comparisons. ## Validation 27 focused UI/macro/assets tests and 9 documentation tests pass. Default/Fast configurations, examples, WebView, Shell, benchmarks, and nightly WebAssembly compile. Clippy, formatting, dependency, and snapshot-pin checks pass. CI adds native and WebAssembly coverage. AI-assisted with Codex. | 20 小时前 | |
docs: deepen GPUI manual and add release guides (#3232) ## Description Deepen the bilingual GPUI Kit documentation into a practical framework manual. The guides now include runnable exercises for state, rendering, custom elements, painting, text, focus, accessibility, and animation, with clearer expected results and troubleshooting. Clarify what the 120 Hz frame budget means, when GPUI requests frames, and how immediate, retained, and hybrid describe different layers. Preserve the five core layers and explain `gpui-pre` as version-aligned GPUI publication. Add focused packaging and Auto Update guides, including a user-level Linux installer, and expand the asset guides for `icon_assets!`, embedded images, `svg()`, and `img()`. Keep the existing sidebar structure. The page order places WebView after Native Extensions; the new navigation labels are Packaging and Auto Update. Generated Markdown now ends with the existing CC BY 4.0 attribution notice. ## Screenshot Not attached. The interactive frame timeline and documentation pages can be reviewed in the site preview. ## How to Test - `script/check-ai docs` — passed (9 recipe fragments and 6 script tests). - `cargo fmt --all -- --check` and `git diff --check` — passed. - `cargo test -p gpui-kit --test ui --features 'test-support component' --locked` — passed (1 test). - Compiled the new runnable documentation examples in existing example packages; temporary verification files were removed. - Checked the affected English and Chinese pages in the local Astro preview, including sidebar order and Markdown license output. - `bun run build` reached static route generation but stopped because the adjacent `gpui-kit-showcases/scripts/validate.ts` checkout is absent locally. The docs CI workflow checks out showcases separately. ## Checklist - [x] I have read the contributing guide and followed the relevant documentation guidance. - [x] Reviewed the AI-assisted examples against the pinned GPUI APIs and compiled the new complete snippets. - [ ] Story app manual tests (not run for this documentation change). - [ ] macOS, Windows, and Linux performance tests (no platform rendering implementation changed). | 11 天前 | |
kit: Add optional gpui-fast backend (#3375) Add an optional `gpui-fast` feature across GPUI Kit’s existing crates: ```toml gpui-kit = { version = "0.7", features = ["gpui-fast"] } ``` No new crates. Preserve the existing `gpui-pre-*` workspace dependencies and default backend; optional Fast dependencies and internal conditional aliases select the engine. Existing application imports and macros continue to work. This is additive, with no breaking changes. Upstream packages still compile because Cargo features are additive. ## Public API - **gpui-kit:** `gpui-fast` selects Fast for core, platforms, and enabled layers. - **gpui-base, gpui-component, gpui-kit-assets, gpui-fps, gpui-wry, gpui-shell, gpui-component-shell:** `gpui-fast` selects the matching backend for standalone consumers. - **gpui-component-macros:** `#[derive(IntoPlot)]` additionally recognizes direct Fast dependencies. - Gallery and examples expose the same feature for comparisons. ## Validation 27 focused UI/macro/assets tests and 9 documentation tests pass. Default/Fast configurations, examples, WebView, Shell, benchmarks, and nightly WebAssembly compile. Clippy, formatting, dependency, and snapshot-pin checks pass. CI adds native and WebAssembly coverage. AI-assisted with Codex. | 20 小时前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 14 天前 | ||
| 2 天前 | ||
| 28 天前 | ||
| 14 天前 | ||
| 20 小时前 | ||
| 5 天前 | ||
| 12 天前 | ||
| 7 天前 | ||
| 14 天前 | ||
| 1 个月前 | ||
| 20 小时前 | ||
| 10 天前 | ||
| 10 天前 | ||
| 20 小时前 | ||
| 14 天前 | ||
| 14 天前 | ||
| 8 天前 | ||
| 14 天前 | ||
| 14 天前 | ||
| 28 天前 | ||
| 20 小时前 | ||
| 11 天前 | ||
| 20 小时前 |