guise-ui 1.5.3

A component library for gpui, Zed's GPU-accelerated UI framework: a themed palette, sizing tokens, 130+ composable components, a reactive state layer, and an in-app Safari-style inspector.
Documentation
//! `Motioned::animate` — a clip on an element's own style.
//!
//! [`Animated`](super::Animated) wraps its child in a `div`, which is right
//! when the child isn't styleable but wrong when the animated thing has a
//! layout contract with its parent: a wrapper is a new flex item, and a
//! `w_full` child suddenly measures against it instead of the row it was in.
//!
//! For anything that is `Styled` — a `div`, a container, the box a component
//! already sits in — this puts the sampled values on that element directly
//! and adds nothing to the tree.

use std::time::Duration;

use gpui::prelude::*;
use gpui::{Animation, AnimationElement, AnimationExt, ElementId, SharedString};

use super::Clip;

pub trait Motioned: IntoElement + Styled + Sized + 'static {
  /// Play a clip once, from the moment this element first lays out.
  ///
  /// Changing the id replays it — that is the only way to restart a
  /// mounted one-shot, and what a "preview" button hands you.
  fn animate(self, id: impl Into<ElementId>, clip: impl Into<Clip>) -> AnimationElement<Self> {
    let clip = clip.into();
    let total = clip.total_ms();

    // An endless clip has no duration to give gpui, so it runs as a
    // repeating animation over one pass — two when it alternates, so the
    // there-and-back parity survives the wrap.
    let (span, repeats) = if total.is_finite() {
      // Zero would divide by zero inside gpui's animation element; a
      // millisecond lands on the final frame immediately, which is what
      // an empty clip should look like anyway.
      (total.max(1.0), false)
    } else {
      let passes = if clip.alternates() { 2.0 } else { 1.0 };
      ((clip.iteration_ms() * passes).max(1.0), true)
    };

    let mut animation = Animation::new(Duration::from_secs_f32(span / 1000.0));
    if repeats {
      animation = animation.repeat();
    }
    // The clock stays linear and the clip does its own easing: gpui
    // debug-asserts the easing slot into 0..=1, and half these curves
    // overshoot on purpose.
    self.with_animation(id, animation, move |el, t| clip.sample(t * span).apply(el))
  }

  /// Play the clip only while `condition` holds.
  ///
  /// `.when(cond, |el| el.animate(..))` cannot work: `animate` changes the
  /// element's type and `when` has to hand back the type it was given. This
  /// keeps the type stable by running an empty clip when the condition is
  /// false, which samples nothing and sets nothing.
  ///
  /// The two states get different element ids, so a clip starts from its
  /// beginning every time the condition turns on rather than resuming a
  /// clock that ran while nobody was looking.
  fn animate_when(
    self,
    condition: bool,
    id: impl Into<ElementId>,
    clip: impl Into<Clip>,
  ) -> AnimationElement<Self> {
    let id = ElementId::NamedChild(
      Box::new(id.into()),
      SharedString::new_static(if condition { "on" } else { "off" }),
    );
    let clip = if condition {
      clip.into()
    } else {
      Clip::default()
    };
    self.animate(id, clip)
  }
}

impl<E: IntoElement + Styled + 'static> Motioned for E {}