Port · Parity: every page matches gpui 0.2.2's; the rules re-tested
Every page and scripted state was shot with gpui 0.2.2 and with the port, light and dark, full width and at 280px, judged pair by pair by agents with skeptics, and every page's log diffed. What differed is fixed, each with a test that fails without it where the test platform can show it: - The gallery's harness takes posted mouse moves (gpui_macos listens to a tracking area), posts each drag move twice (it no longer replays a held drag), and waits three frames before each shot. - Rolling digits: a block column, so taffy 0.13 lends the cell no baseline, and a line of whole device pixels, since gpui snaps each length on its own. DescriptionList's row aligns ends. - Margins that taffy 0.13 collapses through a plain block: the drawer's pull and snap, the stagger and transition rise, the dialog and palette rises, the fullscreen dialog's slide. - An Input keeps room to type in a squeezed row; the field rows drop w_full, whose reason was taffy 0.9's. AGENTS.md's rules that named gpui 0.2.2 or taffy 0.9 were re-tested against both sources and rewritten, and new ones say what the port changed. TASKS.md's tags follow: what the new gpui makes possible is prove. Captions, doc comments and README say what holds now.
This commit is contained in:
1 parent
3189df013f
commit
3f1597fa28
44 files changed
+535
-138
No files matched your search
@@ -47,6 +47,7 @@ async fn shoot_all(
|
||||
.enumerate()
|
||||
.filter(|(ix, _)| only.is_none_or(|only| only == *ix));
|
||||
let gallery = window.update(cx, |_, window, _| number(window))??;
|
||||
window.update(cx, |_, window, _| take_posted_moves(window))??;
|
||||
for (ix, page) in chosen {
|
||||
for (choice, name) in [(Choice::Light, "light"), (Choice::Dark, "dark")] {
|
||||
window.update(cx, |gallery, window, cx| {
|
||||
@@ -68,6 +69,7 @@ async fn shoot_all(
|
||||
format!("-{segment}")
|
||||
};
|
||||
let path = dir.join(format!("{}-{name}{suffix}.png", page.slug));
|
||||
script::shown(window.into(), cx).await?;
|
||||
snapshot(gallery, &path)?;
|
||||
log::info!("capture: wrote {}", path.display());
|
||||
if offset >= max {
|
||||
@@ -88,9 +90,9 @@ async fn shoot_all(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A gpui window's number, as AppKit and CoreGraphics know it.
|
||||
/// A gpui window's AppKit window.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn number(window: &Window) -> Result<u32> {
|
||||
fn native(window: &Window) -> Result<cocoa::base::id> {
|
||||
use cocoa::base::id;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
|
||||
@@ -100,14 +102,33 @@ pub fn number(window: &Window) -> Result<u32> {
|
||||
let RawWindowHandle::AppKit(appkit) = handle.as_raw() else {
|
||||
bail!("window is not an AppKit window");
|
||||
};
|
||||
let number: i64 = unsafe {
|
||||
let view = appkit.ns_view.as_ptr() as id;
|
||||
let ns_window: id = msg_send![view, window];
|
||||
msg_send![ns_window, windowNumber]
|
||||
};
|
||||
Ok(unsafe { msg_send![appkit.ns_view.as_ptr() as id, window] })
|
||||
}
|
||||
|
||||
/// A gpui window's number, as AppKit and CoreGraphics know it.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn number(window: &Window) -> Result<u32> {
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
let number: i64 = unsafe { msg_send![native(window)?, windowNumber] };
|
||||
u32::try_from(number).context("window number out of range")
|
||||
}
|
||||
|
||||
/// Takes posted mouse moves; gpui's tracking area takes only real ones.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn take_posted_moves(window: &Window) -> Result<()> {
|
||||
use cocoa::{appkit::NSWindow, base::YES};
|
||||
|
||||
unsafe { native(window)?.setAcceptsMouseMovedEvents_(YES) };
|
||||
log::info!("capture: the window takes posted mouse moves");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn take_posted_moves(_: &Window) -> Result<()> {
|
||||
bail!("capture needs macOS window APIs")
|
||||
}
|
||||
|
||||
/// This process's frontmost window above the normal level: a popover or menu.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn popup_number() -> Result<u32> {
|
||||
|
||||
@@ -125,7 +125,7 @@ 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.
|
||||
// Keeps drawing while a demo window is key.
|
||||
inactive_frame_interval: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn descriptions(cx: &mut App) -> impl IntoElement + use<> {
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().w_64().child(
|
||||
div().w_64().min_w_0().child(
|
||||
DescriptionList::new()
|
||||
.stacked()
|
||||
.item("Ship to", "12 Harbour Street, Wellington 6011, New Zealand")
|
||||
|
||||
@@ -69,7 +69,7 @@ pub fn select(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
)),
|
||||
)
|
||||
.child(blocked(
|
||||
"NativeSelect: gpui 0.2.2 has no native popup menu, so Select draws its own.",
|
||||
"NativeSelect: gpui has no native popup menu, so Select draws its own.",
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ pub fn skip_link(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
|
||||
),
|
||||
)
|
||||
.child(blocked(
|
||||
"LiveRegion / Announcer, AccessibleIcon, AriaLabel, ScreenReaderOnly: gpui 0.2.2 exposes no accessibility tree, so no label or live region reaches a screen reader.",
|
||||
"LiveRegion / Announcer, AccessibleIcon, AriaLabel, ScreenReaderOnly: not yet shown: gpui builds an AccessKit tree from elements with an id and a role, and no screen reader here reads it.",
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -321,6 +321,6 @@ pub fn shapes(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.child(blocked("Pinch: gpui 0.2.2 forwards no magnify event, so a trackpad pinch never reaches a view.", cx)),
|
||||
.child(blocked("Pinch: gpui hands a trackpad pinch to on_pinch, and the viewport does not zoom on it yet.", cx)),
|
||||
)
|
||||
}
|
||||
@@ -23,20 +23,20 @@ const SCRIPT: &[Step] = &[
|
||||
Step::Hover("scroll-area"),
|
||||
Step::Wait(300),
|
||||
Step::Shot("scroll-hover"),
|
||||
Step::DownAt("scrollbar", 313.0, 12.0),
|
||||
Step::DragTo("scrollbar", 313.0, 110.0),
|
||||
Step::UpAt("scrollbar", 313.0, 110.0),
|
||||
Step::DownAt("scrollbar", 310.0, 12.0),
|
||||
Step::DragTo("scrollbar", 310.0, 110.0),
|
||||
Step::UpAt("scrollbar", 310.0, 110.0),
|
||||
Step::Shot("scrollbar-drag"),
|
||||
Step::DownAt("scrollbar", 313.0, 110.0),
|
||||
Step::DragTo("scrollbar", 313.0, 12.0),
|
||||
Step::UpAt("scrollbar", 313.0, 12.0),
|
||||
Step::DownAt("sticky", 313.0, 12.0),
|
||||
Step::DragTo("sticky", 313.0, 76.0),
|
||||
Step::UpAt("sticky", 313.0, 76.0),
|
||||
Step::DownAt("scrollbar", 310.0, 110.0),
|
||||
Step::DragTo("scrollbar", 310.0, 12.0),
|
||||
Step::UpAt("scrollbar", 310.0, 12.0),
|
||||
Step::DownAt("sticky", 310.0, 12.0),
|
||||
Step::DragTo("sticky", 310.0, 76.0),
|
||||
Step::UpAt("sticky", 310.0, 76.0),
|
||||
Step::Shot("sticky-pushed"),
|
||||
Step::DownAt("sticky", 313.0, 76.0),
|
||||
Step::DragTo("sticky", 313.0, 12.0),
|
||||
Step::UpAt("sticky", 313.0, 12.0),
|
||||
Step::DownAt("sticky", 310.0, 76.0),
|
||||
Step::DragTo("sticky", 310.0, 12.0),
|
||||
Step::UpAt("sticky", 310.0, 12.0),
|
||||
Step::Click("collapse-toggle"),
|
||||
Step::Wait(400),
|
||||
Step::Shot("collapsed"),
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn render(cx: &App) -> impl IntoElement + use<> {
|
||||
cx,
|
||||
)
|
||||
.child(blocked(
|
||||
"PrintButton: gpui 0.2.2 has no print API.",
|
||||
"PrintButton: gpui has no print API.",
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
|
||||
@@ -234,7 +234,7 @@ pub fn backdrops(cx: &mut App) -> impl IntoElement + use<> {
|
||||
),
|
||||
)
|
||||
.child(blocked(
|
||||
"Lottie Player is blocked: gpui 0.2.2 paints paths without the clip masks, mattes, blend modes and radial gradients Lottie files lean on, so frames would come out wrong.",
|
||||
"Lottie Player is blocked: gpui paints paths without the clip masks, mattes, blend modes and radial gradients Lottie files lean on, so frames would come out wrong.",
|
||||
cx,
|
||||
))
|
||||
}
|
||||
@@ -246,6 +246,6 @@ pub fn reorder(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
)
|
||||
.child(div().w_96().child(list))
|
||||
.child(Caption::new(
|
||||
"Motion is gpui's with_animation, timed by motion::duration and eased by the curves in motion. Spring is motion::spring. CountUp is typography::AnimatedNumber::count_up. A collapse is layout::Collapsible. A scale entrance is blocked: gpui 0.2.2 transforms only icons.",
|
||||
"Motion is gpui's with_animation, timed by motion::duration and eased by the curves in motion. Spring is motion::spring. CountUp is typography::AnimatedNumber::count_up. A collapse is layout::Collapsible. A scale entrance is blocked: gpui transforms only svgs.",
|
||||
))
|
||||
}
|
||||
@@ -197,7 +197,7 @@ pub fn backdrop(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
|
||||
pub fn visually_hidden(cx: &App) -> impl IntoElement + use<> {
|
||||
section("VisuallyHidden", "Text for screen readers only.", cx).child(blocked(
|
||||
"Blocked: gpui 0.2.2 has no accessibility tree to speak to.",
|
||||
"Not yet shown: gpui builds an AccessKit tree from elements with an id and a role, and no screen reader here reads it.",
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ pub fn notification(cx: &App) -> impl IntoElement + use<> {
|
||||
cx,
|
||||
)
|
||||
.child(blocked(
|
||||
"Blocked: an unbundled app has no notification center, and gpui 0.2.2 has no notification API.",
|
||||
"Not yet shown: gpui posts a system notification only from an app bundle, and the gallery runs unbundled.",
|
||||
cx,
|
||||
))
|
||||
}
|
||||
@@ -29,7 +29,7 @@ struct Owned(bool);
|
||||
pub fn platform(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
section(
|
||||
"Platform Style · IconThemeProvider · VibrancyBackground",
|
||||
"The theme's platform decides window buttons and key caps, and starts as the system's. An icon theme lays an owner's rules over Ely's file icons. A frosted pane lets a blurred window's backdrop through; gpui 0.2.2 blurs as vibrancy on macOS and acrylic on Windows, and has no Mica.",
|
||||
"The theme's platform decides window buttons and key caps, and starts as the system's. An icon theme lays an owner's rules over Ely's file icons. A frosted pane lets a blurred window's backdrop through; gpui blurs as vibrancy on macOS and acrylic on Windows, and sets Mica on Windows 11.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -75,7 +75,7 @@ pub fn sections(window: &mut Window, cx: &mut App) -> Vec<AnyElement> {
|
||||
.child(inspect())
|
||||
.child(probe(
|
||||
"inspect-sample",
|
||||
div().child(boxed(div().m_3().p_4()).child("A box to pick")),
|
||||
boxed(div().m_3().p_4()).child("A box to pick"),
|
||||
)),
|
||||
)
|
||||
.into_any_element(),
|
||||
|
||||
@@ -150,7 +150,7 @@ fn narrow(cx: &App) -> gpui::Div {
|
||||
pub fn truncate(cx: &App) -> impl IntoElement + use<> {
|
||||
section(
|
||||
"Truncate / Ellipsis",
|
||||
"One line, an ellipsis at the end, in the text style around it. It holds in flex boxes, where gpui's own .truncate() clips.",
|
||||
"One line, an ellipsis at the end, in the text style around it, cut by the shaped line at the width its box gets.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use futures::future::{Either, select};
|
||||
use gpui::{AsyncApp, Bounds, Keystroke, Pixels, Point, WindowHandle, point, px};
|
||||
|
||||
use crate::{
|
||||
@@ -53,6 +54,8 @@ pub enum Step {
|
||||
}
|
||||
|
||||
const FRAME: Duration = Duration::from_millis(120);
|
||||
/// How long a shot waits for each frame of the window it shoots.
|
||||
const SHOWN: Duration = Duration::from_secs(5);
|
||||
/// How long an out-of-process file panel may take to open.
|
||||
const PANEL: Duration = Duration::from_secs(10);
|
||||
const DRAG_STEPS: u32 = 10;
|
||||
@@ -148,10 +151,13 @@ pub async fn play(
|
||||
last.x + (goal.x - last.x) * t,
|
||||
last.y + (goal.y - last.y) * t,
|
||||
);
|
||||
send(window, Mouse::Drag, at, cx)?;
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(16))
|
||||
.await;
|
||||
// Each move twice, as gpui 0.2.2 replayed a held drag; gpui_macos replays only under a real button.
|
||||
for _ in 0..2 {
|
||||
send(window, Mouse::Drag, at, cx)?;
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(16))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
last = goal;
|
||||
}
|
||||
@@ -188,12 +194,14 @@ pub async fn play(
|
||||
}
|
||||
Step::Shot(name) => {
|
||||
let file = path(name);
|
||||
shown(window.into(), cx).await?;
|
||||
snapshot(window.update(cx, |_, window, _| number(window))??, &file)?;
|
||||
log::info!("script: wrote {}", file.display());
|
||||
}
|
||||
Step::ShotWindow(key, name) => {
|
||||
let file = path(name);
|
||||
let handle = opened(key, cx)?;
|
||||
shown(handle, cx).await?;
|
||||
snapshot(handle.update(cx, |_, window, _| number(window))??, &file)?;
|
||||
log::info!("script: wrote {}", file.display());
|
||||
}
|
||||
@@ -247,6 +255,24 @@ pub async fn play(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits three frames: the first draws what the steps left, and AppKit shows it by the third, since gpui presents without waiting on the GPU.
|
||||
pub async fn shown(window: gpui::AnyWindowHandle, cx: &mut AsyncApp) -> Result<()> {
|
||||
for _ in 0..3 {
|
||||
let (drawn, frame) = futures::channel::oneshot::channel();
|
||||
window.update(cx, |_, window, _| {
|
||||
window.on_next_frame(move |_, _| drawn.send(()).unwrap_or_default())
|
||||
})?;
|
||||
match select(frame, cx.background_executor().timer(SHOWN)).await {
|
||||
Either::Left((Ok(()), _)) => {}
|
||||
Either::Left((Err(_), _)) => anyhow::bail!("the window dropped its frame callback"),
|
||||
Either::Right(_) => {
|
||||
anyhow::bail!("the window drew no frame in {SHOWN:?}; is it hidden?")
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Presses a key without holding the root view; Enter and Space also release through AppKit, since components pick on release.
|
||||
fn press(window: gpui::AnyWindowHandle, stroke: Keystroke, cx: &mut AsyncApp) -> Result<()> {
|
||||
let release = match stroke.key.as_str() {
|
||||
|
||||
Reference in new issue
Block a user