Fix animations in the component preview (#33673)

Daniel Sauble created

Fixes #33869

The Animation page in the Component Preview had a few issues.

* The animations only ran once, so you couldn't watch animations below
the fold.
* The offset math was wrong, so some animated elements were rendered
outside of their parent container.
* The "animate in from right" elements were defined with an initial
`.left()` offset, which overrode the animation behavior.

I made fixes to address these issues. In particular, every time you
click the active list item, it renders the preview again (which causes
the animations to run again).

Before:


https://github.com/user-attachments/assets/a1fa2e3f-653c-4b83-a6ed-c55ca9c78ad4

After:


https://github.com/user-attachments/assets/3623bbbc-9047-4443-b7f3-96bd92f582bf

Release Notes:

- N/A

Change summary

crates/ui/src/styles/animation.rs       | 18 +++++++++---------
crates/zed/src/zed/component_preview.rs | 16 ++++++++++++++--
2 files changed, 23 insertions(+), 11 deletions(-)

Detailed changes

crates/ui/src/styles/animation.rs 🔗

@@ -109,7 +109,7 @@ impl Component for Animation {
     fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
         let container_size = 128.0;
         let element_size = 32.0;
-        let left_offset = element_size - container_size / 2.0;
+        let offset = container_size / 2.0 - element_size / 2.0;
         Some(
             v_flex()
                 .gap_6()
@@ -129,7 +129,7 @@ impl Component for Animation {
                                             .id("animate-in-from-bottom")
                                             .absolute()
                                             .size(px(element_size))
-                                            .left(px(left_offset))
+                                            .left(px(offset))
                                             .rounded_md()
                                             .bg(gpui::red())
                                             .animate_in(AnimationDirection::FromBottom, false),
@@ -148,7 +148,7 @@ impl Component for Animation {
                                             .id("animate-in-from-top")
                                             .absolute()
                                             .size(px(element_size))
-                                            .left(px(left_offset))
+                                            .left(px(offset))
                                             .rounded_md()
                                             .bg(gpui::blue())
                                             .animate_in(AnimationDirection::FromTop, false),
@@ -167,7 +167,7 @@ impl Component for Animation {
                                             .id("animate-in-from-left")
                                             .absolute()
                                             .size(px(element_size))
-                                            .left(px(left_offset))
+                                            .top(px(offset))
                                             .rounded_md()
                                             .bg(gpui::green())
                                             .animate_in(AnimationDirection::FromLeft, false),
@@ -186,7 +186,7 @@ impl Component for Animation {
                                             .id("animate-in-from-right")
                                             .absolute()
                                             .size(px(element_size))
-                                            .left(px(left_offset))
+                                            .top(px(offset))
                                             .rounded_md()
                                             .bg(gpui::yellow())
                                             .animate_in(AnimationDirection::FromRight, false),
@@ -211,7 +211,7 @@ impl Component for Animation {
                                             .id("fade-animate-in-from-bottom")
                                             .absolute()
                                             .size(px(element_size))
-                                            .left(px(left_offset))
+                                            .left(px(offset))
                                             .rounded_md()
                                             .bg(gpui::red())
                                             .animate_in(AnimationDirection::FromBottom, true),
@@ -230,7 +230,7 @@ impl Component for Animation {
                                             .id("fade-animate-in-from-top")
                                             .absolute()
                                             .size(px(element_size))
-                                            .left(px(left_offset))
+                                            .left(px(offset))
                                             .rounded_md()
                                             .bg(gpui::blue())
                                             .animate_in(AnimationDirection::FromTop, true),
@@ -249,7 +249,7 @@ impl Component for Animation {
                                             .id("fade-animate-in-from-left")
                                             .absolute()
                                             .size(px(element_size))
-                                            .left(px(left_offset))
+                                            .top(px(offset))
                                             .rounded_md()
                                             .bg(gpui::green())
                                             .animate_in(AnimationDirection::FromLeft, true),
@@ -268,7 +268,7 @@ impl Component for Animation {
                                             .id("fade-animate-in-from-right")
                                             .absolute()
                                             .size(px(element_size))
-                                            .left(px(left_offset))
+                                            .top(px(offset))
                                             .rounded_md()
                                             .bg(gpui::yellow())
                                             .animate_in(AnimationDirection::FromRight, true),

crates/zed/src/zed/component_preview.rs 🔗

@@ -105,6 +105,7 @@ enum PreviewPage {
 struct ComponentPreview {
     active_page: PreviewPage,
     active_thread: Option<Entity<ActiveThread>>,
+    reset_key: usize,
     component_list: ListState,
     component_map: HashMap<ComponentId, ComponentMetadata>,
     components: Vec<ComponentMetadata>,
@@ -188,6 +189,7 @@ impl ComponentPreview {
         let mut component_preview = Self {
             active_page,
             active_thread: None,
+            reset_key: 0,
             component_list,
             component_map: component_registry.component_map(),
             components: sorted_components,
@@ -265,8 +267,13 @@ impl ComponentPreview {
     }
 
     fn set_active_page(&mut self, page: PreviewPage, cx: &mut Context<Self>) {
-        self.active_page = page;
-        cx.emit(ItemEvent::UpdateTab);
+        if self.active_page == page {
+            // Force the current preview page to render again
+            self.reset_key = self.reset_key.wrapping_add(1);
+        } else {
+            self.active_page = page;
+            cx.emit(ItemEvent::UpdateTab);
+        }
         cx.notify();
     }
 
@@ -690,6 +697,7 @@ impl ComponentPreview {
                     component.clone(),
                     self.workspace.clone(),
                     self.active_thread.clone(),
+                    self.reset_key,
                 ))
                 .into_any_element()
         } else {
@@ -1041,6 +1049,7 @@ pub struct ComponentPreviewPage {
     component: ComponentMetadata,
     workspace: WeakEntity<Workspace>,
     active_thread: Option<Entity<ActiveThread>>,
+    reset_key: usize,
 }
 
 impl ComponentPreviewPage {
@@ -1048,6 +1057,7 @@ impl ComponentPreviewPage {
         component: ComponentMetadata,
         workspace: WeakEntity<Workspace>,
         active_thread: Option<Entity<ActiveThread>>,
+        reset_key: usize,
         // languages: Arc<LanguageRegistry>
     ) -> Self {
         Self {
@@ -1055,6 +1065,7 @@ impl ComponentPreviewPage {
             component,
             workspace,
             active_thread,
+            reset_key,
         }
     }
 
@@ -1155,6 +1166,7 @@ impl ComponentPreviewPage {
         };
 
         v_flex()
+            .id(("component-preview", self.reset_key))
             .size_full()
             .flex_1()
             .px_12()