| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
resizable: Let an application build its own resize handle (#3302) ## Description `resize_handle_appearance()` says it is there for "a hand-rolled handle in an application", but an application could not build one. `resize_handle` was `#[doc(hidden)]` and took no listener beyond its own `on_drag`; `ResizeHandleContext` has no public constructor, so the appearance could not be put on anything else either. This makes the handle a public, standalone part, the way `SliderThumb` is. Resizable and the Dock keep their behavior; only their `on_drag` constructors change, to take a `&T` (see below). - `resize_handle(id, axis)` is public in `gpui-base`. `gpui_component::resize_handle` installs the divider appearance, as `h_resizable` does. - `ResizeHandle` implements `InteractiveElement`, `StatefulInteractiveElement` and `InteractiveElementExt`. The drag still goes through the handle's own `on_drag`; `on_drag_move`, `on_hover`, `on_double_click` and the rest attach to the band itself. Base still owns the band, the cursor, `inside(HandleEdge)` and the Idle → Hovered → Pressed → Dragging progression the appearance reads. - The band carries `test_support()`, so `window.find(id)` finds a handle. Why: an application sidebar has a hand-written edge handle with the same three-level indicator — the same 20 / 28 / 44 px at 0.35 / 0.6 / 0.9. It needs a double click that restores the default width, and a hover its hover-to-peek drawer counts as its own. `h_resizable` itself does not fit a collapsible fixed-width sidebar (it keeps panels proportional as the container resizes, and hides a panel without a transition), so the handle is the part it can share. ## Public API ### `gpui-base` - `pub fn resize_handle<T: 'static, E: 'static + Render>(id: impl Into<ElementId>, axis: Axis) -> ResizeHandle<T, E>` — no longer hidden, signature unchanged; a band that resizes whatever it edges. - `pub struct ResizeHandle<T, E>` — now exported from the crate root. - `impl InteractiveElement`, `impl StatefulInteractiveElement` and `impl InteractiveElementExt` for `ResizeHandle<T, E>` — listeners attach to the band. The handle's own `on_drag` shadows `StatefulInteractiveElement::on_drag`. - `pub fn on_drag(self, value: T, f: impl Fn(&T, &Point<Pixels>, &mut Window, &mut App) -> Entity<E> + 'static) -> Self` — the drag carries the `T` itself instead of an `Rc<T>`, and the constructor is handed a `&T` the way GPUI's own `on_drag` is. GPUI tells drags apart by payload type, so a caller's `on_drag_move::<T>` never heard a drag carrying `Rc<T>`. ### `gpui-component` - `pub fn resize_handle<T: 'static, E: 'static + Render>(id: impl Into<ElementId>, axis: Axis) -> ResizeHandle<T, E>` — the Base handle with `resize_handle_appearance()` installed. - `resizable::{HandleEdge, ResizeHandle}` — re-exported. ## Breaking Changes `ResizeHandle::on_drag` was `#[doc(hidden)]`; its constructor now takes `&T` instead of `Rc<T>`: ```diff - .on_drag(DragPanel, |drag, _, _, cx| cx.new(|_| drag.deref().clone())) + .on_drag(DragPanel, |drag, _, _, cx| cx.new(|_| drag.clone())) ``` ## How to Test 1. `cargo test -p gpui-base --lib resizable` — `a_callers_listeners_reach_the_band` hovers, double clicks and drags a standalone handle, and checks the container stops reading as hovered (the band occludes), the double click fires once, a drag reports moves without clicking, and the appearance still sees Idle → Hovered → Pressed → Dragging → Idle. 2. `cargo test -p gpui-component --lib -- theme:: resizable dock` — the Dock and the divider appearance, unchanged. 3. `cargo run -- resizable` / `cargo run -- dock`: dividers look and behave as before. ## 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. --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Floyd Wang <gassnake999@gmail.com> | 1 天前 | |
dock: Remove the tiles canvas (#3036) The dock had three container shapes: `Split`, `Tabs`, and `Tiles`, a canvas of freely positioned panels. Only Longbridge's "custom layout" ever used the canvas, and it is moving into that application as an ordinary dock panel, so this removes the third shape from every layer: the layout tree and its edits, the persisted schema (`PanelInfo::Tiles`, `TileMeta`), `DockArea`'s reconciliation and zoom, the component skin and its theme fields, the shell's script API, the `example-tiles` program, and the docs. What a host needs instead is one small, general hook: `gpui_component::dock::Panel::title_bar` (default `true`). A panel that carries its own chrome returns `false`, and a tab group holding only that panel draws no title bar above it. A group with several panels still draws its tabs. `DropTarget` loses its `Canvas` variant and becomes a struct — a host-owned drop can only land on a tab group now — read through `node()` and `placement()`. ## Breaking Changes Describing a layout: ```diff - DockLayout::tiles().tile(panel, bounds) - DockLayout::tiles().tile_view(panel_handle(panel), bounds, cx) + // Place panels in tab groups; a freeform canvas is now a panel the host implements. + DockLayout::tabs().panel_view(panel_handle(panel), cx) ``` Editing a live area: ```diff - area.add_tile(panel, DockPlacement::Center, bounds, window, cx); - area.add_tile_view(handle, DockPlacement::Center, bounds, window, cx); - area.zoomed_tile(); + area.add_panel(panel, DockPlacement::Center, None, window, cx); + area.add_panel_view(handle, DockPlacement::Center, None, window, cx); + area.zoomed_group(); ``` Reading a tree: ```diff match node.kind() { PaneRef::Split { .. } => .., PaneRef::Tabs { .. } => .., - PaneRef::Tiles { panels } => .., } - InsertTarget::Tile { node, bounds } - TilePanel - tree.set_tile_bounds(panel, bounds); - tree.bring_to_front(panel); ``` Persisted state (a host that stored tiles must migrate the `"tiles"` info tag itself): ```diff - PanelInfo::Tiles { metas: Vec<TileMeta> } // serde tag "tiles" - TileMeta { bounds, z_index } ``` Renderers and contexts: ```diff impl DockAreaRenderer for MySkin { fn tab_group_renderer(&self) -> Rc<dyn TabGroupRenderer> { .. } - fn tiles_renderer(&self) -> Rc<dyn TilesRenderer> { .. } } - impl TilesRenderer for MySkin { .. } - TileContext, TilesState, TilesEvent, ResizeSide, DRAG_BAR_HEIGHT, HANDLE_SIZE ``` Host-owned drops: ```diff DockEvent::DragDrop { item, target } => match target { - DropTarget::Canvas => .., - DropTarget::Group { node, placement } => .., + target => (target.node(), target.placement()), } ``` Skin settings and theme: ```diff - skin.set_tiles_scrollbar_mode(Some(ScrollbarMode::Always), cx); - cx.theme().tiles // ThemeColor, "tiles.background" in theme JSON - cx.theme().tile_grid_size - cx.theme().tile_shadow - cx.theme().tile_radius ``` Shell, Rust side (`gpui_kit::shell::dock`): ```diff - DockChrome::tile_drag_bar / DockChrome::tile_resize_handles - DockCommand::MoveTile / ResizeTile / RaiseTile / ToggleTileZoom / CloseTile - tile_data(tile, cx) ``` Shell script API: ```diff - area.add_panel(view, { name, placement, bounds: { x, y, width, height } }) + area.add_panel(view, { name, placement, size }) - dock_area(area).tile_drag_bar(tile => ..).tile_resize_handles(tile => ..) - element.move_tile(tile) / resize_tile(tile, side) / raise_tile(tile) / toggle_tile_zoom(tile) / close_tile(tile) - DockTile, TileResizeSide ``` New: ```diff pub trait Panel: gpui_base::dock::Panel { + /// Whether the tab group draws a title bar above this panel when it is + /// the only panel in its group. + fn title_bar(&self, cx: &App) -> bool { true } } ``` --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 27 天前 | |
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> | 16 天前 | |
dock: Remove the tiles canvas (#3036) The dock had three container shapes: `Split`, `Tabs`, and `Tiles`, a canvas of freely positioned panels. Only Longbridge's "custom layout" ever used the canvas, and it is moving into that application as an ordinary dock panel, so this removes the third shape from every layer: the layout tree and its edits, the persisted schema (`PanelInfo::Tiles`, `TileMeta`), `DockArea`'s reconciliation and zoom, the component skin and its theme fields, the shell's script API, the `example-tiles` program, and the docs. What a host needs instead is one small, general hook: `gpui_component::dock::Panel::title_bar` (default `true`). A panel that carries its own chrome returns `false`, and a tab group holding only that panel draws no title bar above it. A group with several panels still draws its tabs. `DropTarget` loses its `Canvas` variant and becomes a struct — a host-owned drop can only land on a tab group now — read through `node()` and `placement()`. ## Breaking Changes Describing a layout: ```diff - DockLayout::tiles().tile(panel, bounds) - DockLayout::tiles().tile_view(panel_handle(panel), bounds, cx) + // Place panels in tab groups; a freeform canvas is now a panel the host implements. + DockLayout::tabs().panel_view(panel_handle(panel), cx) ``` Editing a live area: ```diff - area.add_tile(panel, DockPlacement::Center, bounds, window, cx); - area.add_tile_view(handle, DockPlacement::Center, bounds, window, cx); - area.zoomed_tile(); + area.add_panel(panel, DockPlacement::Center, None, window, cx); + area.add_panel_view(handle, DockPlacement::Center, None, window, cx); + area.zoomed_group(); ``` Reading a tree: ```diff match node.kind() { PaneRef::Split { .. } => .., PaneRef::Tabs { .. } => .., - PaneRef::Tiles { panels } => .., } - InsertTarget::Tile { node, bounds } - TilePanel - tree.set_tile_bounds(panel, bounds); - tree.bring_to_front(panel); ``` Persisted state (a host that stored tiles must migrate the `"tiles"` info tag itself): ```diff - PanelInfo::Tiles { metas: Vec<TileMeta> } // serde tag "tiles" - TileMeta { bounds, z_index } ``` Renderers and contexts: ```diff impl DockAreaRenderer for MySkin { fn tab_group_renderer(&self) -> Rc<dyn TabGroupRenderer> { .. } - fn tiles_renderer(&self) -> Rc<dyn TilesRenderer> { .. } } - impl TilesRenderer for MySkin { .. } - TileContext, TilesState, TilesEvent, ResizeSide, DRAG_BAR_HEIGHT, HANDLE_SIZE ``` Host-owned drops: ```diff DockEvent::DragDrop { item, target } => match target { - DropTarget::Canvas => .., - DropTarget::Group { node, placement } => .., + target => (target.node(), target.placement()), } ``` Skin settings and theme: ```diff - skin.set_tiles_scrollbar_mode(Some(ScrollbarMode::Always), cx); - cx.theme().tiles // ThemeColor, "tiles.background" in theme JSON - cx.theme().tile_grid_size - cx.theme().tile_shadow - cx.theme().tile_radius ``` Shell, Rust side (`gpui_kit::shell::dock`): ```diff - DockChrome::tile_drag_bar / DockChrome::tile_resize_handles - DockCommand::MoveTile / ResizeTile / RaiseTile / ToggleTileZoom / CloseTile - tile_data(tile, cx) ``` Shell script API: ```diff - area.add_panel(view, { name, placement, bounds: { x, y, width, height } }) + area.add_panel(view, { name, placement, size }) - dock_area(area).tile_drag_bar(tile => ..).tile_resize_handles(tile => ..) - element.move_tile(tile) / resize_tile(tile, side) / raise_tile(tile) / toggle_tile_zoom(tile) / close_tile(tile) - DockTile, TileResizeSide ``` New: ```diff pub trait Panel: gpui_base::dock::Panel { + /// Whether the tab group draws a title bar above this panel when it is + /// the only panel in its group. + fn title_bar(&self, cx: &App) -> bool { true } } ``` --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 27 天前 | |
plot: Move plot primitives to gpui-base (#3258) ## Summary This PR moves the chart primitives from `gpui-component` into `gpui-base` as `gpui_base::plot`: - Scales (`ScaleLinear`, `ScaleBand`, `ScalePoint`, `ScaleOrdinal`), shapes (`Bar`, `Line`, `Area`, `Arc`, `Pie`, `Stack`, `RadialLine`, Sankey layout), `PlotAxis`, `Grid`, `PlotLabel`, `PathCaches`, and the `Plot` trait. - The element behind every plot, previously generated by `#[derive(IntoPlot)]`, is now written once as `gpui_base::plot::PlotElement<P>`. - Hover tracking (`TooltipState`, `PlotHover`, the focus fade and pointer glide) moves with it. Its timing comes from a new `PlotMotion` on `gpui_base::Theme::plot`, which is motionless by default like `ScrollbarMotion`. `gpui-component` keeps the presentation: `plot::tooltip::{Tooltip, CrossLine, Dot}`, the charts, and `#[derive(IntoPlot)]`. It re-exports `gpui_base::plot` unchanged, and its theme projects its motion tokens onto `gpui_base::Theme::plot`. Existing imports and chart behavior are unchanged. ## Motivation Plotting geometry and hover behavior are not design decisions; only colors, the tooltip box and timing are. With them inside `gpui-component`, a design system built on `gpui-base` alone — such as [gpui-omarchy](https://github.com/huacnlee/gpui-omarchy), which deliberately does not depend on the component facade — has to copy about 5,000 lines of scales, shapes and hover code to offer charts. After this change it depends on `gpui_kit::base::plot`, projects its own `PlotMotion`, and writes only its styled charts and tooltip. ## API review for 0.7.0 Moving plot into gpui-base puts it under the Base seam rules, so this PR also tidies the API before 0.7.0: - **No presentation in Base.** `ScaleBand` no longer caps bands at 30px (the cap moves to `BarChart`/`CandlestickChart::max_band_width`, 30px by default), and the x-axis gutter is `axis_gutter(font_size)` instead of a fixed `AXIS_GAP`. - **Order-independent builders.** `PlotAxis` labels were placed when `x_label` was called, so calling it before `x`/`x_label_side` dropped or misplaced them; labels are now placed at paint time. - **Vocabulary.** Hover fade is `progress`, not `focus` (focus means keyboard focus); `StrokeStyle` (a curve, not a dash style) is `Curve`. - **Evolvable records.** Types with public fields are `#[non_exhaustive]`. - **A named value bound.** `PlotValue` (`f32`, `f64`, `Decimal`) replaces the hidden `Sealed`, so charts now accept `f32`. - **Consistent shapes.** `stroke`/`fill`/`dot_fill` take `impl Into<Background>`; `Arc` paints without per-call radius overrides. `TooltipState` keeps its name, fields and `Plot::tooltip_state`. ## Public API ### gpui-base - `pub mod plot` — the primitives moved from `gpui_component::plot`. - `plot::PlotElement<P: Plot + 'static>`, `PlotElement::new(plot: P) -> Self` — the element a `Plot` renders through. - `plot::hover_progress(window: &mut Window, cx: &mut App) -> f32` — how far the enclosing plot's hover has faded in (`1` outside a plot). - `plot::is_hover_entering(window: &mut Window, cx: &mut App) -> bool` — first hovered frame (`false` outside a plot). - `plot::pointer_spring(cx: &App) -> Spring` — the spring a crosshair, band or dot follows the hovered datum with. - `PlotHover::progress(&self) -> f32` — hover fade from 0 to 1. - `PlotMotion` — `with_pointer(Spring)`, `with_enter(Transition)`, `with_exit(Transition)`, `pointer()`, `enter()`, `exit()`; motionless by default. - `PlotTheme` — `new()`, `with_motion(PlotMotion)`, `motion()`; `gpui_base::Theme::plot: PlotTheme`. - `plot::PlotValue` — sealed value trait for scales and charts: `f32`, `f64`, `rust_decimal::Decimal` (feature `decimal`). - `ScaleLinear::new(domain: impl IntoIterator<Item = T>, range: [f32; 2])`, same shape for `ScalePoint::new` and `ScaleBand::new`. - `Scale::nearest_index(&self, tick: f32) -> usize`. - `ScaleBand::max_band_width(self, width: f32) -> Self` (unset by default); `ScaleBand::band_count(self, count: usize) -> Self` is now public. - `plot::axis_gutter(font_size: Pixels) -> f32` — the height an x-axis with labels of that size needs (18 at the default 10px). - `PlotAxis::stroke` and `Grid::stroke` take `impl Into<Background>`; `Grid::x`/`y` take `impl IntoIterator<Item = impl Into<Pixels>>`; `Grid: Default`. - `plot::Curve { Natural, Linear, StepAfter }`; `Line::curve(Curve)`, `Area::curve(Curve)`. - `dot_fill(impl Into<Background>)`, `dot_stroke(impl Into<Hsla>)`. - `Arc::paint(arc, fill: impl Into<Background>, bounds, window)`, `Arc::paint_cached(arc, fill, bounds, cache, window)`, `Arc::contains(arc, position, bounds)`. - `ArcData::new(data, index, value, start_angle, end_angle) -> Self`. - `decimal` feature. ### gpui-component - `plot` re-exports `gpui_base::plot::*`; `plot::tooltip` keeps `Tooltip`, `CrossLine`, `CrossLineAxis`, `Dot` and re-exports `TooltipState`, `PlotHover`. - `Tooltip::progress(self, progress: f32) -> Self` — override the overlay fade. - `BarChart::max_band_width(self, width: impl Into<Pixels>) -> Self`, `CandlestickChart::max_band_width(...)` — default 30px. - Charts accept `f32` values (bound is `PlotValue`). - `decimal` forwards to `gpui-base/decimal`; the theme projects its motion tokens onto `gpui_base::Theme::plot`. ### gpui-component-macros - `#[derive(IntoPlot)]` generates `impl IntoElement for T { type Element = PlotElement<T>; … }`. ## Breaking Changes Charts (`LineChart`, `BarChart`, …) keep their API. These affect custom plots built on the primitives. ### `StrokeStyle` is renamed `Curve` `StrokeStyle` chose how a `Line` or `Area` connects its points — a curve interpolation (d3's `curveNatural` / `curveLinear` / `curveStepAfter`), not a dash or solid stroke style, which is what "stroke style" usually means. It is now `plot::Curve` with the same variants, and the shape builder `stroke_style(..)` is `curve(..)`. `StrokeStyle` and `stroke_style` are removed without deprecated aliases. ```diff - use gpui_kit::base::plot::StrokeStyle; + use gpui_kit::base::plot::Curve; - Line::new().stroke_style(StrokeStyle::StepAfter) + Line::new().curve(Curve::StepAfter) - Area::new().stroke_style(StrokeStyle::Linear) + Area::new().curve(Curve::Linear) ``` `LineChart`/`AreaChart`'s `.natural()`, `.linear()` and `.step_after()` are unchanged. ### Other changes Deprecated aliases for this release: `AXIS_GAP` (→ `axis_gutter`), `dot_fill_color`, `dot_stroke_color`, `PlotHover::focus`, `Tooltip::focus`. ```diff - let y = ScaleLinear::new(values.collect(), vec![height, 0.]); + let y = ScaleLinear::new(values, [height, 0.]); - where Y: Copy + PartialOrd + Num + ToPrimitive + Sealed + where Y: PlotValue - let ix = scale.least_index(tick); + let ix = scale.nearest_index(tick); - arc.paint(&data, color, None, Some(radius), &bounds, window); + Arc::new().outer_radius(radius).paint(&data, color, &bounds, window); - Grid::new().y(ticks.collect()) + Grid::new().y(ticks) ``` - `ScaleBand::band_width` no longer caps at 30px; set `max_band_width` for the old width. - `least_index_with_domain` and the hidden `Sealed` are removed. - `TooltipState`, `AxisText`, `label::Text`, `ArcData`, `StackPoint`, `StackSeries`, `SankeyLink`, `SankeyNodeLayout`, `SankeyLinkLayout`, `SankeyGraph` are `#[non_exhaustive]`: build them with their constructors. - `PlotAxis::default()` now equals `new()` (x-axis line shown). - `#[derive(IntoPlot)]` sets `type Element = PlotElement<Self>` instead of implementing `Element` on the plot. - `gpui_component::plot::tooltip::track_hover` (hidden) is removed. - `gpui_base::Theme` gains a `plot` field: ```diff gpui_base::Theme { appearance, tokens, scrollbar, resizable, + plot: gpui_base::PlotTheme::default(), } ``` ## Test Plan - [x] `cargo build --all` - [x] `cargo clippy -p gpui-base -p gpui-component -p gpui-component-macros -p gpui-kit -p gpui-component-story --all-targets -- --deny warnings` - [x] `cargo test -p gpui-base plot` (55 passed), `cargo test -p gpui-component --lib -- chart plot progress theme` (87 passed), including new tests for builder-order-independent axis labels, `max_band_width`, `f32` scales and charts, reversed ranges and band range start - [x] `cargo check -p gpui-component --features decimal` - [x] `typos` on changed paths - [x] gpui-omarchy builds its charts on `gpui_base::plot` alone (no `gpui-component`) via a path patch to this branch (before the API cleanup; to be re-checked) - [ ] Story gallery: line, bar, area, pie, radar, candlestick and sankey charts render as before; hover fades in and out and the crosshair glides between data - [ ] Website: `/base/plot` and `/zh-CN/base/plot` render and link from the Base index `cargo fmt --check` reports only `crates/component/src/form/tests.rs`, which is already unformatted on `main` and untouched here. The code moves and the new `PlotElement`, hover and `PlotMotion` code were written with Claude Code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> | 11 天前 | |
docs: expand GPUI core guides and improve documentation UI (#3225) ## Description Expand the English and Chinese GPUI guides into a source-checked reference for GPUI Kit users. The new and revised pages cover entity ownership, render lifecycles, custom elements and painting, actions and events, tasks, globals, styling, text, accessibility, animation, WebAssembly, and related APIs. Examples use practical module boundaries and distinguish API guarantees from design guidance. Improve documentation navigation, cross-links, and code examples. Add an animated memory diagram to the SharedString guide. Make article links visibly identifiable and give long tables a distinct header and alternating row backgrounds. Add a CC BY 4.0 attribution notice for eligible GPUI Kit documentation prose and original illustrations, while preserving the repository's existing Apache-2.0 permissions for software and code examples. ## Screenshot The documentation UI changes are visible in the built site: links have blue text and underlines, table headers have a filled background, and alternate body rows have a subtle tint. The SharedString guide contains a responsive SVG comparison with reduced-motion support. ## How to Test From `website/`: ```sh bun run build bun run test:seo bun run test:showcases bun run test:versioned-examples ``` All commands passed. `git diff --check` passed as well. ## Checklist - [x] Reviewed the documentation and code examples against the current GPUI and GPUI Kit APIs. - [x] Verified the English and Chinese guides and the generated documentation site. - [x] No Rust public API or platform-specific runtime behavior changed. | 13 天前 | |
plot: Keep finished appears across remounts with `PlotAppearScope` (#3308) ## Summary A plot's appear (#3296) lives in its element state. GPUI drops element state for anything not painted in a frame, so a plot inside a virtual list — `list`, `uniform_list`, a `List` row — forgets its appear once it scrolls past the overdraw and draws in again every time it comes back. The only workaround so far was `.appear(false)`, which gives up the appear entirely. `PlotAppearScope` wraps the region that repaints its plots on and off: ```rust PlotAppearScope::new(("transcript", conversation_id), list(state, render_row).flex_1()) ``` - A plot inside it that finished appearing is remembered by its global element id and appear generation. Painted again after a gap, it gets `PlotAppear::complete()` straight away: no `Presence` tracking and no frames, and hover is not held back. - A new `appear_generation` still replays, and a plot taken away mid-appear draws in again from the start. - The memory is the scope's own element state, so it lasts exactly while the scope is painted every frame. Closing the view, or giving the scope another id (name it after the content), draws its plots in afresh. - The innermost scope wins. The scope takes no part in layout: it hands on its child's `LayoutId`, so a self-sizing child such as `list(..).flex_1()` keeps working unchanged. - Without a scope, behavior is unchanged. It works for every `Plot`: the built-in charts and hand-written plots, since the check sits in `track_appear`, which `PlotElement` already calls. ## Public API ### `gpui-base` ```rust pub struct PlotAppearScope // remembers finished appears across remounts impl PlotAppearScope { pub fn new(id: impl Into<ElementId>, child: impl IntoElement) -> Self } impl IntoElement for PlotAppearScope impl Element for PlotAppearScope ``` Re-exported as `gpui_component::plot::PlotAppearScope`. ## Breaking Changes None. ## Test plan - [x] `cargo test -p gpui-base -p gpui-component` - [x] New tests in `plot::appear`: - A remount inside a scope after the appear finished is complete at once and asks for no frames. - A new generation still replays. - A remount before the appear finished replays. - A renamed scope, or one that stops being painted, forgets. - Without a scope, a remount replays as before. - [x] `cargo clippy -p gpui-base -p gpui-component --lib --tests -- --deny warnings` - [x] Docs: `website/base/plot.md`, `website/component/chart.md` (en + zh-CN), `release-notes.md` This is a `gpui-base` change: the appear state it extends already lives there. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 9 天前 | |
mobile: Add touch selection handles and an edit menu to Input and TextView (#3073) ## Summary On a touch screen, a long press in an `Input`, `Textarea`, `Editor` or selectable `TextView` selects the word under the finger and keeps following the finger while it stays down. Lifting it opens an iOS-style edit menu over the selection with the commands that apply — Cut, Copy, Paste, Select All — and puts a grab handle at each end of the selection. Dragging a handle moves that end while the other stays put; a multi-line input scrolls when the finger reaches its edge. <img width="632" height="784" alt="image" src="https://github.com/user-attachments/assets/4c17490c-55b1-47ed-bda1-a711bec78857" /> - **Base** owns the behavior. `SelectionEdge` and `TouchSelectionSnapshot` carry the caret line boxes at both ends of a touch selection. The input engine (`input/base/touch.rs`) selects on long press, remembers the range the gesture made, drags an end through `begin/update/end_edge_drag`, and drops the handles and menu as soon as anything else moves the selection (tap, typing, arrow keys, `Escape`, blur). The menu steps aside while content scrolls under a finger and returns when it lifts; tapping the selected text brings it back. - The **window text selection** keeps a long-press selection with handles; participants report where they painted their selection ends via `TextSelectionRegistration::with_selection_edges` (TextView does this for every inline). A handle drag rebuilds a word or select-all selection from those ends and extends it. `TextSelection::{touch_selection, observe_touch_selection, begin/update/end_edge_drag, select_all, close_edit_menu, register_touch_ui}` expose this to a presentation layer, and a press on a registered handle/menu surface no longer clears the selection it belongs to. - **GPUI Component** draws the presentation: `SelectionHandle` (selection-color bar and knob, 44px touch target, claims the `TouchDragEvent`, mouse-draggable too) and `EditMenu` (popover surface of ghost `Button`s with `Separator`s, `Positioner`ed above the selection clear of the knobs, flipping below when there is no room). `Input` renders its own overlay from `InputContextMenuCapabilities` (read-only offers Copy/Select All; masked keeps its value out of the clipboard; Paste is offered without reading the clipboard, which shows a banner on iOS). `Root` mounts `WindowTouchSelectionOverlay` for the window selection with Copy and Select All. - **Example**: `cargo run -p touch_selection` drives the whole thing from a desktop — a `Long press` button per control injects the gesture, after which the handles drag with the mouse. Use it for manual regression checks. - Docs: Input, TextView, mobile and base text-selection pages in `en` and `zh-CN`. ## Test Plan - `cargo test -p gpui-base --lib` (925 passed): long press selects a word and the release opens the menu; empty input places a caret with a menu; handle drag moves one end only and crossing swaps ends; touch selection goes away when something else moves the selection; scrolling closes the menu and keeps the handles; TextView long press keeps handles which drag the selection, including after select-all, and a press on a registered menu surface leaves the selection alone. - `cargo test -p gpui-kit --features test-support --test touch_selection` (5 passed): real `Root` window, finds and clicks the `Copy` / `Select All` menu buttons, drags the end handle with `TouchDragEvent`s and with the mouse. - `cargo test -p gpui-component --lib`, `cargo clippy --all-targets -- --deny warnings`, `cargo fmt --check`, `typos` all pass. - Verified visually on macOS through injected `LongPressEvent`s: single-line Input, multi-line Textarea sweep, and TextView all show the menu and the handles in the expected places. - Not run on an iOS device or simulator; the native gesture delivery through the gpui-mobile host still needs an end-to-end check. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 23 天前 | |
text_view: Follow the container's text color so rich text reads in filled bubbles (#3329) Closes #3326 ## Description `TextView` paints its own text color on its root, so it does not inherit the color a `Bubble` sets on its content. In a `Filled` bubble, Markdown is drawn in the theme `foreground` on the `primary` surface: white on white in dark mode. Links (`primary`) disappear too, and code backgrounds (`accent`/`muted`) are tuned for the page, not for a `primary` surface. The first revision added `BubbleVariant::text_view_style(cx)`, which callers passed to every `TextView` in a bubble. That repeated the bubble's variant at each call site and would have needed the same method on every other colored container, so the fix now lives in `TextView` itself: - **Follow the container's text color.** A `TextView` without an explicit `.style()` takes the text color its container sets (`window.text_style().color`, or its own `.text_color()`). - **Same-polarity surfaces** (Secondary, Destructive, a popover): only the body text changes. Links, code and borders keep the theme colors. - **Inverted surfaces** (`Filled`, whose `primary` fill is dark on a light page and light on a dark one): when the inherited color is more than 0.6 away from the style's foreground in Oklab lightness, links, muted text (block quotes), code and inline-code backgrounds, borders, selection, the table header and body backgrounds are all derived from it. The dark flag flips, so todo checkboxes pick the right icon. The installed syntax highlighter is left out, since its colors are made for the page. - **Opt-in in Base, on in Component.** `TextViewDefaults::with_inherit_text_color(true)` enables it. Component turns it on because its Root sets the theme foreground. A Base-only window that sets no root text color would otherwise inherit GPUI's default black. `Bubble` needs no change, since it already sets each variant's text color. ## Screenshot Filled, Secondary and Destructive bubbles with a link, inline code, a block quote, a todo list, a fenced code block and a table, in the light and dark themes, read correctly. Before this change, the Filled bubble's text, link and table were invisible in dark mode. ## Public API ### gpui-base - `gpui_base::TextViewDefaults::with_inherit_text_color(self, inherit: bool) -> Self`: makes text views without an explicit style follow the text color their container sets, adapting every color on an inverted surface. - `gpui_base::TextViewDefaults::inherit_text_color(&self) -> bool`: whether that is enabled. ### gpui-component No items are added. Text views now follow the container's text color by default, because Component installs `with_inherit_text_color(true)`. ## How to Test - `cargo test -p gpui-base -p gpui-component` - `text_color_of_a_matching_surface_only_replaces_the_body_text` and `text_color_of_an_inverted_surface_derives_every_color_from_it` cover the color derivation in both themes. - `text_view_follows_the_text_color_of_its_container` checks that a view under a container with `primary_foreground` text resolves to that color with a flipped dark flag, and that an explicit `.style()` still wins. - Manually: render `Bubble::new().child(TextView::markdown("reply", text))` with links, code, quotes, todos and a table in each variant, and toggle the theme. ## 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. - [x] Passed `cargo run` for story tests related to the changes. - [ ] Tested macOS, Windows and Linux platforms performance (if the change is platform-specific) (not platform-specific) 🤖 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 天前 | |
base: move the native examples into their own package (#2963) `gpui-base` declares `gpui_platform` for every non-wasm target, but only the examples use it. For targets that supply their own backend through `Application::with_platform` (iOS, Android), the build fails inside `gpui_platform`. Following what `examples/wasm` already does, this moves the native showcase and motion examples into their own package, `gpui-base-examples`, which includes the same shared code with `#[path]`; the `gpui-base` manifest no longer mentions `gpui_platform`. `src/bin/components.rs` and `examples/components.rs` were two entry points to the same showcase and become the package's `components` bin; the README commands are updated accordingly. Verified: - `cargo check -p gpui-base-examples --bins` - `cargo check -p gpui-base --examples --tests --benches` - `cargo check -p gpui-base --target aarch64-apple-ios-sim` and `--target wasm32-unknown-unknown` - `cargo check --workspace` - `cargo fmt --all --check` Closes #2962 --------- Co-authored-by: Jason Lee <huacnlee@gmail.com> | 1 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 天前 | ||
| 27 天前 | ||
| 16 天前 | ||
| 27 天前 | ||
| 11 天前 | ||
| 13 天前 | ||
| 9 天前 | ||
| 23 天前 | ||
| 7 天前 | ||
| 1 个月前 |