Port · Build: Zed's gpui at 1a28cff

gpui and gpui_platform now come from Zed's repo by git at 1a28cff,
the code gpui-pre 0.3.7 republishes, so the gallery can later build
for the browser. gpui takes stacker, as 0.2.2 always did, and
gpui_platform takes font-kit, without which macOS draws no text;
runtime_shaders moves to gpui_platform.

The API moved: focus, blur and Tab steps take cx, Corner is Anchor,
shaped lines paint with an alignment, defer_draw takes a content
mask, the inspector registers a factory, async updates return their
value, and wrapped lines are shared, not cloned. Updates through a
window context go through the window, so a closed window still ends
them as it did.

Where the new gpui behaves differently, Ely keeps its old intent.
Holds and reveals under a resting pointer survive typing through
gpui's input-modality-independent hover; previews the keyboard also
moves keep the default, and the calendar's leave ignores a key press.
Enter reaches a button inside the code editor, since gpui now arms a
key click on the press. The event logger skips a lone modifier. A
video and an image grid keep their shape in a flex column under
taffy 0.13, as a picture does.

AGENTS.md records the new stack and the rules this port changes; the
rest of the old gpui and taffy rules are re-tested in T44c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-28 20:30:17 -04:00
1 parent e49c93bb6f
commit 94deeaed52
199 files changed
+2513 -2643

No files matched your search

+1 -1
View File
@@ -26,7 +26,7 @@ pub fn run(
Theme::update(cx, |theme| theme.reduced_motion = true);
cx.spawn(async move |cx| {
let outcome = shoot_all(window, &dir, only, scripted, cx).await;
if let Err(error) = outcome.and_then(|()| cx.update(|cx| cx.quit())) {
if let Err(error) = outcome.map(|()| cx.update(|cx| cx.quit())) {
log::error!("capture failed: {error:#}");
std::process::exit(1);
}
+7 -3
View File
@@ -22,8 +22,8 @@ use std::path::PathBuf;
use anyhow::{Context as _, Result, bail};
use ely_gpui_component::Assets;
use gpui::{
App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, TitlebarOptions,
WindowBounds, WindowHandle, WindowOptions, actions, point, px, size,
App, AppContext, Bounds, KeyBinding, Menu, MenuItem, TitlebarOptions, WindowBounds,
WindowHandle, WindowOptions, actions, point, px, size,
};
actions!(gallery, [Quit, About, LightMode, DarkMode, NewWindow]);
@@ -38,6 +38,7 @@ fn menus(cx: &mut App) {
MenuItem::separator(),
MenuItem::action("Quit Ely", Quit),
],
disabled: false,
},
Menu {
name: "View".into(),
@@ -45,6 +46,7 @@ fn menus(cx: &mut App) {
MenuItem::action("Light", LightMode),
MenuItem::action("Dark", DarkMode),
],
disabled: false,
},
]);
cx.set_dock_menu(vec![MenuItem::action("New Window", NewWindow)]);
@@ -103,7 +105,7 @@ fn parse_args() -> Result<Args> {
fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let args = parse_args()?;
Application::new()
gpui_platform::application()
.with_assets(Assets)
.run(move |cx: &mut App| {
ely_gpui_component::init(cx);
@@ -123,6 +125,8 @@ fn main() -> Result<()> {
traffic_light_position: Some(point(px(16.0), px(18.0))),
}),
window_min_size: Some(size(px(880.0), px(560.0))),
// Draws every frame while a demo window is key, as gpui 0.2.2 did.
inactive_frame_interval: None,
..Default::default()
};
let window = cx
+1
View File
@@ -105,6 +105,7 @@ pub fn styles(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
offset: point(px(shadow.x), px(shadow.y)),
blur_radius: px(shadow.blur),
spread_radius: px(shadow.spread),
inset: false,
})
.collect(),
);
+1 -4
View File
@@ -185,7 +185,7 @@ pub fn realtime(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
cx.background_executor()
.timer(Duration::from_millis(900))
.await;
let pushed = cx.update(|cx| {
cx.update(|cx| {
stream.update(cx, |(values, pushed), cx| {
let last = values.last().copied().unwrap_or(60.0);
values.push((last + (rng() - 0.5) * 10.0).clamp(20.0, 100.0));
@@ -194,9 +194,6 @@ pub fn realtime(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
cx.notify();
})
});
if pushed.is_err() {
return;
}
}
})
.detach();
+3 -3
View File
@@ -199,7 +199,7 @@ pub fn streaming(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
cx.background_executor().timer(Duration::from_millis(600)).await;
loop {
cx.background_executor().timer(Duration::from_millis(45)).await;
let more = feeder.update(cx, |stream, cx| {
let more = cx.update(|_, cx| feeder.update(cx, |stream, cx| {
let next = (stream.arrived + 7).min(ANSWER.len());
stream.arrived = (next..=ANSWER.len())
.find(|at| ANSWER.is_char_boundary(*at))
@@ -207,8 +207,8 @@ pub fn streaming(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
stream.live = stream.arrived < ANSWER.len();
cx.notify();
stream.live
});
if !matches!(more, Ok(true)) {
}));
if !matches!(more.and_then(|inner| inner), Ok(true)) {
break;
}
}
+1 -2
View File
@@ -221,8 +221,7 @@ pub fn playground(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
},
cx,
)
})
.ok();
});
})
.detach();
});
+1 -2
View File
@@ -117,8 +117,7 @@ pub fn panel(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
settings.enhancing = false;
settings.suggestion = Some(text.into());
})
})
.ok();
});
})
.detach();
})
+1 -1
View File
@@ -298,7 +298,7 @@ pub fn focus(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
.on_click(move |_, window, cx| {
change(&show, cx, |focusing| focusing.field = !focusing.field);
let focus = focus.clone();
window.defer(cx, move |window, _| window.focus(&focus));
window.defer(cx, move |window, cx| window.focus(&focus, cx));
}),
)
.when(shown, |column| column.child(Input::new(&field))),
+1 -1
View File
@@ -67,7 +67,7 @@ pub fn infinite(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let more = more.clone();
cx.spawn(async move |cx| {
cx.background_executor().timer(Duration::from_millis(700)).await;
cx.update(|cx| set(&more, (rows + PAGE, false), cx)).ok();
cx.update(|cx| set(&more, (rows + PAGE, false), cx));
})
.detach();
}),
+1 -2
View File
@@ -144,8 +144,7 @@ pub fn tree(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
items.push(item("logo", "logo.svg", Some(&key), false));
items.push(item("inter", "Inter.ttf", Some(&key), false));
})
})
.ok();
});
})
.detach();
})
+17 -13
View File
@@ -4,7 +4,9 @@ use ely_gpui_component::media::{
AudioPlayer, AudioSpectrum, AudioWaveform, MediaControls, PlaybackSpeedControl, Playlist,
Repeat, Track, VolumeControl,
};
use gpui::{App, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
use gpui::{
App, Entity, IntoElement, ParentElement, SharedString, Styled, TaskExt as _, Window, div, px,
};
use crate::{
probe::probe,
@@ -98,19 +100,21 @@ fn tick(state: &Entity<Listening>, window: &mut Window, cx: &mut App) {
.spawn(cx, async move |cx| {
loop {
cx.background_executor().timer(TICK).await;
let going = state.update(cx, |now, cx| {
now.at = (now.at + TICK.mul_f32(now.speed)).min(now.length());
if now.at == now.length() {
match now.repeat {
Repeat::One => now.at = Duration::ZERO,
Repeat::All => now.skip(1),
Repeat::Off if now.song + 1 < SONGS.len() => now.skip(1),
Repeat::Off => now.playing = false,
let going = cx.update(|_, cx| {
state.update(cx, |now, cx| {
now.at = (now.at + TICK.mul_f32(now.speed)).min(now.length());
if now.at == now.length() {
match now.repeat {
Repeat::One => now.at = Duration::ZERO,
Repeat::All => now.skip(1),
Repeat::Off if now.song + 1 < SONGS.len() => now.skip(1),
Repeat::Off => now.playing = false,
}
}
}
now.ticking = now.playing;
cx.notify();
now.playing
now.ticking = now.playing;
cx.notify();
now.playing
})
})?;
if !going {
return anyhow::Ok(());
+12 -8
View File
@@ -1,7 +1,9 @@
use std::{path::Path, time::Duration};
use ely_gpui_component::media::{Cue, Scrubber, SubtitleEditor, VideoPlayer, VideoThumbnailStrip};
use gpui::{App, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
use gpui::{
App, Entity, IntoElement, ParentElement, SharedString, Styled, TaskExt as _, Window, div, px,
};
use crate::{
probe::probe,
@@ -61,13 +63,15 @@ fn tick(state: &Entity<Playback>, window: &mut Window, cx: &mut App) {
.spawn(cx, async move |cx| {
loop {
cx.background_executor().timer(TICK).await;
let going = state.update(cx, |now, cx| {
let step = TICK.mul_f32(now.speed);
now.at = (now.at + step).min(LENGTH);
now.playing &= now.at < LENGTH;
now.ticking = now.playing;
cx.notify();
now.playing
let going = cx.update(|_, cx| {
state.update(cx, |now, cx| {
let step = TICK.mul_f32(now.speed);
now.at = (now.at + step).min(LENGTH);
now.playing &= now.at < LENGTH;
now.ticking = now.playing;
cx.notify();
now.playing
})
})?;
if !going {
return anyhow::Ok(());
+1 -3
View File
@@ -24,9 +24,7 @@ pub fn later<T: 'static>(state: &Entity<T>, value: T, wait: Duration, cx: &mut A
let state = state.clone();
cx.spawn(async move |cx| {
cx.background_executor().timer(wait).await;
if let Err(error) = cx.update(|cx| set(&state, value, cx)) {
log::error!("gallery: the demo closed before its answer: {error:#}");
}
cx.update(|cx| set(&state, value, cx));
})
.detach();
}
+6 -4
View File
@@ -23,11 +23,13 @@ pub fn animated_number(window: &mut Window, cx: &mut App) -> impl IntoElement +
_task: cx.spawn_in(window, async move |ticker, cx| {
loop {
cx.background_executor().timer(STEP).await;
let advanced = ticker.update(cx, |ticker: &mut Ticker, cx| {
ticker.index = (ticker.index + 1) % VALUES.len();
cx.notify();
let advanced = cx.update(|_, cx| {
ticker.update(cx, |ticker: &mut Ticker, cx| {
ticker.index = (ticker.index + 1) % VALUES.len();
cx.notify();
})
});
if advanced.is_err() {
if advanced.and_then(|inner| inner).is_err() {
return;
}
}
+6 -6
View File
@@ -73,7 +73,7 @@ pub fn park(window: WindowHandle<Gallery>, cx: &mut AsyncApp) -> Result<()> {
}
fn opened(key: &str, cx: &mut AsyncApp) -> Result<gpui::AnyWindowHandle> {
cx.update(|cx| Opened::get(key, cx))?
cx.update(|cx| Opened::get(key, cx))
.with_context(|| format!("no window opened as {key}"))
}
@@ -231,15 +231,15 @@ pub async fn play(
}
Step::ExpectClosed(key) => {
let handle = opened(key, cx)?;
let open = cx.update(|cx| cx.windows().contains(&handle))?;
let open = cx.update(|cx| cx.windows().contains(&handle));
anyhow::ensure!(!open, "window {key} is still open");
cx.update(|cx| Opened::take(key, cx))?;
cx.update(|cx| Opened::take(key, cx));
log::info!("script: {key} closed");
}
Step::CloseWindow(key) => {
let handle = opened(key, cx)?;
handle.update(cx, |_, window, _| window.remove_window())?;
cx.update(|cx| Opened::take(key, cx))?;
cx.update(|cx| Opened::take(key, cx));
}
}
cx.background_executor().timer(FRAME).await;
@@ -439,13 +439,13 @@ async fn target_bounds(
cx: &mut AsyncApp,
) -> Result<Bounds<Pixels>> {
let bounds = cx
.update(|cx| Probes::get(key, cx))?
.update(|cx| Probes::get(key, cx))
.with_context(|| format!("probe {key} never rendered"))?;
if window.update(cx, |gallery, _, cx| gallery.reveal(bounds, cx))? {
cx.background_executor().timer(FRAME).await;
}
let bounds = cx
.update(|cx| Probes::get(key, cx))?
.update(|cx| Probes::get(key, cx))
.with_context(|| format!("probe {key} vanished after scrolling"))?;
log::info!("script: probe {key} at {bounds:?}");
Ok(bounds)
+3 -6
View File
@@ -44,7 +44,7 @@ impl Gallery {
}
});
let focus = cx.focus_handle();
window.focus(&focus);
window.focus(&focus, cx);
Self {
page,
pass: 0,
@@ -71,10 +71,7 @@ impl Gallery {
/// Viewport height and furthest scroll, in pixels.
pub fn scroll_extent(&self) -> (Pixels, Pixels) {
(
self.scroll.bounds().size.height,
self.scroll.max_offset().height,
)
(self.scroll.bounds().size.height, self.scroll.max_offset().y)
}
/// Scrolls until `target` sits inside the page. True if it moved.
@@ -87,7 +84,7 @@ impl Gallery {
} else {
return false;
};
let y = (-self.scroll.offset().y + shift).clamp(px(0.0), self.scroll.max_offset().height);
let y = (-self.scroll.offset().y + shift).clamp(px(0.0), self.scroll.max_offset().y);
self.scroll_to(y, cx);
true
}