diff --git a/AGENTS.md b/AGENTS.md index 8311e02..f4eac90 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -109,7 +109,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark. - Set an explicit line height on any box that clips text. gpui's default leading is taller than a tight box, and the clip eats descenders. - gpui's `img` takes its picture's pixel size for any `Auto` side, and its aspect ratio beats percent heights in flow. `Image` pins it absolute at full size, so an `Image` needs a sized box. - Badges, tags and avatars live in `data_display`; forms and shell draw theirs from there, and `data_display` imports neither. Avatars take square pictures. -- `img()` keeps loading state only with an id. Content masks are rectangles, so rounded corners survive only when the image fills its box without cropping. +- `img()` keeps loading state, and moves an animated picture's frames, only with an id. Content masks are rectangles, so rounded corners survive only when the image fills its box without cropping. - gpui reads a `&str` picture source as a web address. A file goes through `Path` (`documents::source` in the crate, `Path::new` in the gallery). - Taffy 0.9's grid rows overgrow tiles that keep a ratio. Tiles in a picture's shape sit in flex rows of `flex_1` cells (`generative::GenerationGrid`). - Rise reads green and fall red unless a component's `red_up` swaps them, as markets in East Asia read. `finance::quotes::moves` picks the pair. diff --git a/TASKS.md b/TASKS.md index 68da51e..762a5ff 100644 --- a/TASKS.md +++ b/TASKS.md @@ -307,3 +307,4 @@ Per-component lines live in `tasks/`. Tags there: | T25e | 2 | PASS | | | T25f | 1 | FAIL | a drag on the turned embedding froze past the plot's edge; which version a diff sets against, and its heading, had no test | | T25f | 2 | PASS | | +| T26a | 1 | FAIL | the viewer's picture had no id, so an animated picture never left its first frame | diff --git a/src/media/tests.rs b/src/media/tests.rs index bbb53dd..1a9db47 100644 --- a/src/media/tests.rs +++ b/src/media/tests.rs @@ -282,6 +282,9 @@ fn a_picture_that_cannot_open_says_so(cx: &mut TestAppContext) { let cx = viewing(std::env::temp_dir().join("ely-media-missing.png"), cx); assert!(cx.debug_bounds("image-viewer-failed").is_some()); assert!(cx.debug_bounds("image-viewer-picture").is_none()); + cx.update(|window, _| window.focus_next()); + let focused = cx.update(|window, cx| window.focused(cx)); + assert!(focused.is_none(), "with nothing to show, nothing takes Tab"); } /// Two thumbnails, the second with no handler; the opens the first heard. diff --git a/src/media/viewer.rs b/src/media/viewer.rs index 77cfd05..3589bec 100644 --- a/src/media/viewer.rs +++ b/src/media/viewer.rs @@ -119,7 +119,6 @@ impl RenderOnce for ImageViewer { } }); } - let focus = tab_stop((self.id.clone(), "focus").into(), true, window, cx); let decoded = window.use_asset::(&self.source, cx); let turns = view.read(cx).turns; let picture: Option> = match decoded { @@ -143,6 +142,12 @@ impl RenderOnce for ImageViewer { let size = image.size(0); (size.width.0 as f32, size.height.0 as f32) }); + let focus = tab_stop( + (self.id.clone(), "focus").into(), + pixels.is_some(), + window, + cx, + ); let theme = cx.theme(); let colors = theme.colors.clone(); let (zoom, offset, stage) = { @@ -252,6 +257,7 @@ impl RenderOnce for ImageViewer { }); let body = match (picture, shown) { (Some(image), Some((size, left, top))) => img(ImageSource::Render(image)) + .id((self.id.clone(), "picture")) .debug_selector(|| "image-viewer-picture".into()) .absolute() .left(Pixels::from(left)) @@ -305,8 +311,9 @@ impl RenderOnce for ImageViewer { .bg(colors.sunken) .border_1() .border_color(transparent_black()) - .track_focus(&focus) - .focus_ring(cx) + .when(pixels.is_some(), |stage| { + stage.track_focus(&focus).focus_ring(cx) + }) .when(overhangs, |stage| stage.cursor_grab()) .child( canvas(