Skip to content

Core Objects

keyed.base

Base classes for drawable stuff.

Lifetime

Represents the lifespan of a drawable object in an animation.

An object will only be drawn if the current frame is within the specified start and end frames.

Parameters:

Name Type Description Default
start float | None

The start frame of the lifetime. Defaults to negative infinity if not provided.

None
end float | None

The end frame of the lifetime. Defaults to positive infinity if not provided.

None

Attributes:

Name Type Description
start

The starting frame of the object's lifetime.

end

The ending frame of the object's lifetime.

Source code in src/keyed/base.py
class Lifetime:
    """Represents the lifespan of a drawable object in an animation.

    An object will only be drawn if the current frame is within the specified start and end frames.

    Args:
        start: The start frame of the lifetime. Defaults to negative infinity if not provided.
        end: The end frame of the lifetime. Defaults to positive infinity if not provided.

    Attributes:
        start: The starting frame of the object's lifetime.
        end: The ending frame of the object's lifetime.
    """

    def __init__(self, start: float | None = None, end: float | None = None):
        self.start = start if start else -float("inf")
        self.end = end if end else float("inf")

    def __contains__(self, frame: int) -> bool:
        """Check if a specific frame is within the lifetime of the object.

        Args:
            frame: The frame number to check.

        Returns:
            True if the frame is within the lifetime, False otherwise.
        """
        return (self.start <= frame) and (self.end >= frame)

Base

Bases: TransformNode

Base class for drawable objects in an animation scene.

Attributes:

Name Type Description
scene Scene

The scene to which the object belongs.

lifetime Lifetime

The lifetime of the object.

Source code in src/keyed/base.py
class Base(TransformNode):
    """Base class for drawable objects in an animation scene.

    Attributes:
        scene: The scene to which the object belongs.
        lifetime: The lifetime of the object.
    """

    scene: Scene
    lifetime: Lifetime

    def __init__(self, scene: Scene | None = None) -> None:
        from .scene import resolve_scene

        self.scene = resolve_scene(scene)
        TransformNode.__init__(self, self.scene.frame)
        self.lifetime = Lifetime()

    @property
    def dependencies(self) -> list[Variable]:
        return self._dependencies

    def draw(self) -> None:
        """Draw the object on the scene."""
        pass

    def _animate(self, property: str, animation: Animation) -> Self:
        """Apply an animation to a property of the shape.

        Note:
            You probably want to directly create a reactive value and provide it
            as an argument for your object rather than using this.

            This is a vestigial method that still exists almost entirely for
            implementing the [Group.write_on][keyed.group.Group.write_on] method.

            It may be removed in the future.

        Args:
            property: The name of the property to animate.
            animation: The animation object defining how the property changes over time.
        """
        p = getattr(self, property)
        assert isinstance(p, Variable)
        setattr(self, property, animation(p, self.frame))
        return self

    def emphasize(
        self,
        buffer: float = 5,
        radius: float = 0,
        fill_color: tuple[float, float, float] = (1, 1, 1),
        color: tuple[float, float, float] = (1, 1, 1),
        alpha: float = 1,
        dash: tuple[Sequence[float], float] | None = None,
        line_width: float = 2,
        draw_fill: bool = True,
        draw_stroke: bool = True,
        operator: cairo.Operator = cairo.OPERATOR_SCREEN,
    ) -> Rectangle:
        """Emphasize the object by drawing a rectangle around it.

        Args:
            buffer: The buffer distance around the object's geometry for the emphasis.
            radius: The corner radius of the emphasized area.
            fill_color: The fill color of the emphasis as an RGB tuple.
            color: The stroke color of the emphasis as an RGB tuple.
            alpha: The alpha transparency of the emphasis.
            dash: The dash pattern for the emphasis outline. Default is solid line.
            line_width: The line width of the emphasis outline.
            draw_fill: Whether to draw the fill of the emphasis.
            draw_stroke: Whether to draw the stroke of the emphasis.
            operator: The compositing operator to use for drawing the emphasis.

        Returns:
            A Rectangle object representing the emphasized area around the original object.

        Notes:
            This creates a Rectangle instance and sets up dynamic expressions to follow the
            geometry of the object as it changes through different frames, applying the specified
            emphasis effects. Emphasis should generally be applied after all animations on the
            original object have been added.
        """
        # TODO: Consider renaming "buffer" to margin.
        from .shapes import Rectangle

        r = Rectangle(
            self.scene,
            color=color,
            x=self.center_x,
            y=self.center_y,
            width=self.width + buffer,
            height=self.height + buffer,
            fill_color=fill_color,
            alpha=alpha,
            dash=dash,
            operator=operator,
            line_width=line_width,
            draw_fill=draw_fill,
            draw_stroke=draw_stroke,
            radius=radius,
        )
        return r

    def set(self, property: str, value: Any, frame: int = ALWAYS) -> Self:
        """Set a property of the object at a specific frame.

        Args:
            property: The name of the property to set.
            value: The new value for the property.
            frame: The frame at which the property value should be set.

        Returns:
            Self

        See Also:
            [keyed.Base.set_literal][keyed.Base.set_literal]
        """
        prop = getattr(self, property)
        new = step(value, frame)(prop, self.frame)
        if isinstance(prop, Variable):
            for p in prop._observers:
                if isinstance(p, Variable):
                    p.observe(new)
                # new.subscribe(p)  # TODO: Using subscribe directly causes color interpolation test to have infinite recursion?

        setattr(self, property, new)
        return self

    def set_literal(self, property: str, value: Any) -> Self:
        """Overwrite a property with a literal value.

        Args:
            property: The name of the property to set.
            value: The new value for the property.

        Returns:
            Self

        See Also:
            [keyed.Base.set][keyed.Base.set]
        """
        setattr(self, property, value)
        return self

    def center(self, frame: int = ALWAYS) -> Self:
        """Center the object within the scene.

        Args:
            frame: The frame at which to center the object.

        Returns:
            self
        """
        self.align_to(self.scene, start=frame, end=frame, direction=ORIGIN, center_on_zero=True)
        return self

    def cleanup(self) -> None:
        return None

    def fade(self, value: HasValue[float], start: int, end: int, ease: EasingFunctionT = linear_in_out) -> Self:
        """Control the object's alpha parameter to fade it in or out.

        Args:
            value: Value to set alpha to.
            start: Frame to start changing alpha.
            end: Frame to finish changing alpha.
            ease: Easing function

        Returns:
            Self
        """
        assert hasattr(self, "alpha")
        self.alpha = Animation(start, end, self.alpha, value, ease=ease)(self.alpha, self.frame)  # type: ignore[assignment]
        return self

    def line_to(
        self, other: Base, self_direction: Direction = RIGHT, other_direction: Direction = LEFT, **line_kwargs: Any
    ) -> Line:
        """Create a line connecting this object to another object.

        Args:
            other: The target object to connect to
            self_direction: Direction for the connection point on this object
            other_direction: Direction for the connection point on the target object
            **line_kwargs: Additional arguments to pass to the [Line][keyed.line.Line] constructor.

        Returns:
            The created Line object
        """
        from .line import Line

        self_point = get_critical_point(self.geom, direction=self_direction)
        other_point = get_critical_point(other.geom, direction=other_direction)
        return Line(self.scene, x0=self_point[0], y0=self_point[1], x1=other_point[0], y1=other_point[1], **line_kwargs)

geom property

geom

Return a reactive value of the transformed geometry.

draw

draw()

Draw the object on the scene.

Source code in src/keyed/base.py
def draw(self) -> None:
    """Draw the object on the scene."""
    pass

emphasize

emphasize(buffer=5, radius=0, fill_color=(1, 1, 1), color=(1, 1, 1), alpha=1, dash=None, line_width=2, draw_fill=True, draw_stroke=True, operator=OPERATOR_SCREEN)

Emphasize the object by drawing a rectangle around it.

Parameters:

Name Type Description Default
buffer float

The buffer distance around the object's geometry for the emphasis.

5
radius float

The corner radius of the emphasized area.

0
fill_color tuple[float, float, float]

The fill color of the emphasis as an RGB tuple.

(1, 1, 1)
color tuple[float, float, float]

The stroke color of the emphasis as an RGB tuple.

(1, 1, 1)
alpha float

The alpha transparency of the emphasis.

1
dash tuple[Sequence[float], float] | None

The dash pattern for the emphasis outline. Default is solid line.

None
line_width float

The line width of the emphasis outline.

2
draw_fill bool

Whether to draw the fill of the emphasis.

True
draw_stroke bool

Whether to draw the stroke of the emphasis.

True
operator Operator

The compositing operator to use for drawing the emphasis.

OPERATOR_SCREEN

Returns:

Type Description
Rectangle

A Rectangle object representing the emphasized area around the original object.

Notes

This creates a Rectangle instance and sets up dynamic expressions to follow the geometry of the object as it changes through different frames, applying the specified emphasis effects. Emphasis should generally be applied after all animations on the original object have been added.

Source code in src/keyed/base.py
def emphasize(
    self,
    buffer: float = 5,
    radius: float = 0,
    fill_color: tuple[float, float, float] = (1, 1, 1),
    color: tuple[float, float, float] = (1, 1, 1),
    alpha: float = 1,
    dash: tuple[Sequence[float], float] | None = None,
    line_width: float = 2,
    draw_fill: bool = True,
    draw_stroke: bool = True,
    operator: cairo.Operator = cairo.OPERATOR_SCREEN,
) -> Rectangle:
    """Emphasize the object by drawing a rectangle around it.

    Args:
        buffer: The buffer distance around the object's geometry for the emphasis.
        radius: The corner radius of the emphasized area.
        fill_color: The fill color of the emphasis as an RGB tuple.
        color: The stroke color of the emphasis as an RGB tuple.
        alpha: The alpha transparency of the emphasis.
        dash: The dash pattern for the emphasis outline. Default is solid line.
        line_width: The line width of the emphasis outline.
        draw_fill: Whether to draw the fill of the emphasis.
        draw_stroke: Whether to draw the stroke of the emphasis.
        operator: The compositing operator to use for drawing the emphasis.

    Returns:
        A Rectangle object representing the emphasized area around the original object.

    Notes:
        This creates a Rectangle instance and sets up dynamic expressions to follow the
        geometry of the object as it changes through different frames, applying the specified
        emphasis effects. Emphasis should generally be applied after all animations on the
        original object have been added.
    """
    # TODO: Consider renaming "buffer" to margin.
    from .shapes import Rectangle

    r = Rectangle(
        self.scene,
        color=color,
        x=self.center_x,
        y=self.center_y,
        width=self.width + buffer,
        height=self.height + buffer,
        fill_color=fill_color,
        alpha=alpha,
        dash=dash,
        operator=operator,
        line_width=line_width,
        draw_fill=draw_fill,
        draw_stroke=draw_stroke,
        radius=radius,
    )
    return r

set

set(property, value, frame=ALWAYS)

Set a property of the object at a specific frame.

Parameters:

Name Type Description Default
property str

The name of the property to set.

required
value Any

The new value for the property.

required
frame int

The frame at which the property value should be set.

ALWAYS

Returns:

Type Description
Self

Self

See Also

[keyed.Base.set_literal][]

Source code in src/keyed/base.py
def set(self, property: str, value: Any, frame: int = ALWAYS) -> Self:
    """Set a property of the object at a specific frame.

    Args:
        property: The name of the property to set.
        value: The new value for the property.
        frame: The frame at which the property value should be set.

    Returns:
        Self

    See Also:
        [keyed.Base.set_literal][keyed.Base.set_literal]
    """
    prop = getattr(self, property)
    new = step(value, frame)(prop, self.frame)
    if isinstance(prop, Variable):
        for p in prop._observers:
            if isinstance(p, Variable):
                p.observe(new)
            # new.subscribe(p)  # TODO: Using subscribe directly causes color interpolation test to have infinite recursion?

    setattr(self, property, new)
    return self

set_literal

set_literal(property, value)

Overwrite a property with a literal value.

Parameters:

Name Type Description Default
property str

The name of the property to set.

required
value Any

The new value for the property.

required

Returns:

Type Description
Self

Self

See Also

[keyed.Base.set][]

Source code in src/keyed/base.py
def set_literal(self, property: str, value: Any) -> Self:
    """Overwrite a property with a literal value.

    Args:
        property: The name of the property to set.
        value: The new value for the property.

    Returns:
        Self

    See Also:
        [keyed.Base.set][keyed.Base.set]
    """
    setattr(self, property, value)
    return self

center

center(frame=ALWAYS)

Center the object within the scene.

Parameters:

Name Type Description Default
frame int

The frame at which to center the object.

ALWAYS

Returns:

Type Description
Self

self

Source code in src/keyed/base.py
def center(self, frame: int = ALWAYS) -> Self:
    """Center the object within the scene.

    Args:
        frame: The frame at which to center the object.

    Returns:
        self
    """
    self.align_to(self.scene, start=frame, end=frame, direction=ORIGIN, center_on_zero=True)
    return self

fade

fade(value, start, end, ease=linear_in_out)

Control the object's alpha parameter to fade it in or out.

Parameters:

Name Type Description Default
value HasValue[float]

Value to set alpha to.

required
start int

Frame to start changing alpha.

required
end int

Frame to finish changing alpha.

required
ease EasingFunctionT

Easing function

linear_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/base.py
def fade(self, value: HasValue[float], start: int, end: int, ease: EasingFunctionT = linear_in_out) -> Self:
    """Control the object's alpha parameter to fade it in or out.

    Args:
        value: Value to set alpha to.
        start: Frame to start changing alpha.
        end: Frame to finish changing alpha.
        ease: Easing function

    Returns:
        Self
    """
    assert hasattr(self, "alpha")
    self.alpha = Animation(start, end, self.alpha, value, ease=ease)(self.alpha, self.frame)  # type: ignore[assignment]
    return self

line_to

line_to(other, self_direction=RIGHT, other_direction=LEFT, **line_kwargs)

Create a line connecting this object to another object.

Parameters:

Name Type Description Default
other Base

The target object to connect to

required
self_direction Direction

Direction for the connection point on this object

RIGHT
other_direction Direction

Direction for the connection point on the target object

LEFT
**line_kwargs Any

Additional arguments to pass to the Line constructor.

{}

Returns:

Type Description
Line

The created Line object

Source code in src/keyed/base.py
def line_to(
    self, other: Base, self_direction: Direction = RIGHT, other_direction: Direction = LEFT, **line_kwargs: Any
) -> Line:
    """Create a line connecting this object to another object.

    Args:
        other: The target object to connect to
        self_direction: Direction for the connection point on this object
        other_direction: Direction for the connection point on the target object
        **line_kwargs: Additional arguments to pass to the [Line][keyed.line.Line] constructor.

    Returns:
        The created Line object
    """
    from .line import Line

    self_point = get_critical_point(self.geom, direction=self_direction)
    other_point = get_critical_point(other.geom, direction=other_direction)
    return Line(self.scene, x0=self_point[0], y0=self_point[1], x1=other_point[0], y1=other_point[1], **line_kwargs)

rotate

rotate(amount, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Rotate the object.

Parameters:

Name Type Description Default
amount HasValue[float]

Amount to rotate by.

required
start int

The frame to start rotating.

ALWAYS
end int

The frame to end rotating.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to rotate.

None
direction Direction

The relative critical point of the center.

ORIGIN

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def rotate(
    self,
    amount: HasValue[float],
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Rotate the object.

    Args:
        amount: Amount to rotate by.
        start: The frame to start rotating.
        end: The frame to end rotating.
        easing: The easing function to use.
        center: The object around which to rotate.
        direction: The relative critical point of the center.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    return self.apply_transform(rotate(start, end, amount, cx, cy, self.frame, easing))

scale

scale(amount, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Scale the object.

Parameters:

Name Type Description Default
amount HasValue[float]

Amount to scale by.

required
start int

The frame to start scaling.

ALWAYS
end int

The frame to end scaling.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to rotate.

None
direction Direction

The relative critical point of the center.

ORIGIN

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def scale(
    self,
    amount: HasValue[float],
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Scale the object.

    Args:
        amount: Amount to scale by.
        start: The frame to start scaling.
        end: The frame to end scaling.
        easing: The easing function to use.
        center: The object around which to rotate.
        direction: The relative critical point of the center.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    return self.apply_transform(scale(start, end, amount, cx, cy, self.frame, easing))

translate

translate(x=0, y=0, start=ALWAYS, end=ALWAYS, easing=cubic_in_out)

Translate the object.

Parameters:

Name Type Description Default
x HasValue[float]

x offset.

0
y HasValue[float]

y offset.

0
start int

The frame to start translating.

ALWAYS
end int

The frame to end translating.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
Source code in src/keyed/transforms.py
def translate(
    self,
    x: HasValue[float] = 0,
    y: HasValue[float] = 0,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
) -> Self:
    """Translate the object.

    Args:
        x: x offset.
        y: y offset.
        start: The frame to start translating.
        end: The frame to end translating.
        easing: The easing function to use.
    """
    return self.apply_transform(translate(start, end, x, y, self.frame, easing))

move_to

move_to(x=None, y=None, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Move object to absolute coordinates.

Parameters:

Name Type Description Default
x HasValue[float] | None

Destination x coordinate

None
y HasValue[float] | None

Destination y coordinate

None
start int

Starting frame, by default ALWAYS

ALWAYS
end int

Ending frame, by default ALWAYS

ALWAYS
easing EasingFunctionT

Easing function, by default cubic_in_out

cubic_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/transforms.py
def move_to(
    self,
    x: HasValue[float] | None = None,
    y: HasValue[float] | None = None,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Move object to absolute coordinates.

    Args:
        x: Destination x coordinate
        y: Destination y coordinate
        start: Starting frame, by default ALWAYS
        end: Ending frame, by default ALWAYS
        easing: Easing function, by default cubic_in_out

    Returns:
        Self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    return self.apply_transform(
        move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing)
    )

align_to

align_to(to, start=ALWAYS, lock=None, end=ALWAYS, from_=None, easing=cubic_in_out, direction=ORIGIN, center_on_zero=False)

Align the object to another object.

Parameters:

Name Type Description Default
to Transformable

The object to align to.

required
start int

Start of animation (begin aligning to the object).

ALWAYS
end int

End of animation (finish aligning to the object at this frame, and then stay there).

ALWAYS
from_ HasValue[GeometryT] | None

Use this object as self when doing the alignment. This is helpful for code animations. It is sometimes desirable to align, say, the top-left edge of one character in a TextSelection to the top-left of another character.

None
easing EasingFunctionT

The easing function to use.

cubic_in_out
direction Direction

The critical point of to and from_to use for the alignment.

ORIGIN
center_on_zero bool

If true, align along the "0"-valued dimensions. Otherwise, only align to on non-zero directions. This is beneficial for, say, centering the object at the origin (which has a vector that consists of two zeros).

False

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def align_to(
    self,
    to: Transformable,
    start: int = ALWAYS,
    lock: int | None = None,
    end: int = ALWAYS,
    from_: HasValue[GeometryT] | None = None,
    easing: EasingFunctionT = cubic_in_out,
    direction: Direction = ORIGIN,
    center_on_zero: bool = False,
) -> Self:
    """Align the object to another object.

    Args:
        to: The object to align to.
        start: Start of animation (begin aligning to the object).
        end: End of animation (finish aligning to the object at this frame, and then stay there).
        from_: Use this object as self when doing the alignment. This is helpful for code
            animations. It is sometimes desirable to align, say, the top-left edge of one
            character in a TextSelection to the top-left of another character.
        easing: The easing function to use.
        direction: The critical point of to and from_to use for the alignment.
        center_on_zero: If true, align along the "0"-valued dimensions. Otherwise, only align to on non-zero
            directions. This is beneficial for, say, centering the object at the origin (which has
            a vector that consists of two zeros).

    Returns:
        self
    """
    # TODO: I'd like to get rid of center_on_zero.
    to_geom = to.geom
    from_geom = from_ if from_ is not None else self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to_geom,
            from_geom,
            frame=self.frame,
            start=start,
            lock=lock,
            end=end,
            ease=easing,
            direction=direction,
            center_on_zero=center_on_zero,
        )
    )

lock_on

lock_on(target, reference=None, start=ALWAYS, end=-ALWAYS, direction=ORIGIN, x=True, y=True)

Lock on to a target.

Parameters:

Name Type Description Default
target Transformable

Object to lock onto

required
reference ReactiveValue[GeometryT] | None

Measure from this object. This is useful for TextSelections, where you want to align to a particular character in the selection.

None
start int

When to start locking on.

ALWAYS
end int

When to end locking on.

-ALWAYS
x bool

If true, lock on in the x dimension.

True
y bool

If true, lock on in the y dimension.

True
Source code in src/keyed/transforms.py
def lock_on(
    self,
    target: Transformable,
    reference: ReactiveValue[GeometryT] | None = None,
    start: int = ALWAYS,
    end: int = -ALWAYS,
    direction: Direction = ORIGIN,
    x: bool = True,
    y: bool = True,
) -> Self:
    """Lock on to a target.

    Args:
        target: Object to lock onto
        reference: Measure from this object. This is useful for TextSelections, where you want to align
            to a particular character in the selection.
        start: When to start locking on.
        end: When to end locking on.
        x: If true, lock on in the x dimension.
        y: If true, lock on in the y dimension.
    """
    reference_ = reference if reference is not None else self.geom
    return self.apply_transform(
        lock_on(
            target=target.geom,
            reference=reference_,
            frame=self.frame,
            start=start,
            end=end,
            direction=direction,
            x=x,
            y=y,
        )
    )

shear

shear(angle_x=0, angle_y=0, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None)

Shear the object.

Parameters:

Name Type Description Default
angle_x HasValue[float]

Angle (in degrees) to shear by along x direction.

0
angle_y HasValue[float]

Angle (in degrees) to shear by along x direction.

0
start int

The frame to start scaling.

ALWAYS
end int

The frame to end scaling.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to rotate.

None

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def shear(
    self,
    angle_x: HasValue[float] = 0,
    angle_y: HasValue[float] = 0,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
) -> Self:
    """Shear the object.

    Args:
        angle_x: Angle (in degrees) to shear by along x direction.
        angle_y: Angle (in degrees) to shear by along x direction.
        start: The frame to start scaling.
        end: The frame to end scaling.
        easing: The easing function to use.
        center: The object around which to rotate.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, ORIGIN)
    return self.apply_transform(
        shear(
            start=start,
            end=end,
            angle_x=angle_x,
            angle_y=angle_y,
            cx=cx,
            cy=cy,
            frame=self.frame,
            ease=easing,
        )
    )

stretch

stretch(scale_x=1, scale_y=1, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Stretch the object.

Parameters:

Name Type Description Default
scale_x HasValue[float]

Amount to scale by in x direction.

1
scale_y HasValue[float]

Amount to scale by in y direction.

1
start int

The frame to start scaling.

ALWAYS
end int

The frame to end scaling.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to rotate.

None
direction Direction

The relative critical point of the center.

ORIGIN

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def stretch(
    self,
    scale_x: HasValue[float] = 1,
    scale_y: HasValue[float] = 1,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Stretch the object.

    Args:
        scale_x: Amount to scale by in x direction.
        scale_y: Amount to scale by in y direction.
        start: The frame to start scaling.
        end: The frame to end scaling.
        easing: The easing function to use.
        center: The object around which to rotate.
        direction: The relative critical point of the center.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    return self.apply_transform(
        stretch(
            start=start,
            end=end,
            scale_x=scale_x,
            scale_y=scale_y,
            cx=cx,
            cy=cy,
            frame=self.frame,
            ease=easing,
        )
    )

match_size

match_size(other, match_x=True, match_y=True, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Scale object dimensions to match another object.

Parameters:

Name Type Description Default
other Transformable

Object whose width and height to match.

required
match_x bool

If True, match width.

True
match_y bool

If True, match height.

True
start int

Frame at which scaling begins.

ALWAYS
end int

Frame at which scaling stops varying.

ALWAYS
easing EasingFunctionT

Easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to scale.

None
direction Direction

The relative critical point of the center.

ORIGIN

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def match_size(
    self,
    other: Transformable,
    match_x: bool = True,
    match_y: bool = True,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Scale object dimensions to match another object.

    Args:
        other: Object whose width and height to match.
        match_x: If True, match width.
        match_y: If True, match height.
        start: Frame at which scaling begins.
        end: Frame at which scaling stops varying.
        easing: Easing function to use.
        center: The object around which to scale.
        direction: The relative critical point of the center.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    matrix = match_size(
        start=start,
        end=end,
        match_x=match_x,
        match_y=match_y,
        target_width=other.width,
        target_height=other.height,
        original_width=self.width,
        original_height=self.height,
        cx=cx,
        cy=cy,
        frame=self.frame,
        ease=easing,
    )
    return self.apply_transform(matrix)

next_to

next_to(to, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, offset=10.0, direction=LEFT)

Align the object to another object.

Parameters:

Name Type Description Default
to Transformable

The object to align to.

required
start int

Start of animation (begin aligning to the object).

ALWAYS
end int

End of animation (finish aligning to the object at this frame, and then stay there).

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
offset HasValue[float]

Distance between objects (in pixels).

10.0
direction Direction

The critical point of to and self to use for the alignment.

LEFT

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def next_to(
    self,
    to: Transformable,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    offset: HasValue[float] = 10.0,
    direction: Direction = LEFT,
) -> Self:
    """Align the object to another object.

    Args:
        to: The object to align to.
        start: Start of animation (begin aligning to the object).
        end: End of animation (finish aligning to the object at this frame, and then stay there).
        easing: The easing function to use.
        offset: Distance between objects (in pixels).
        direction: The critical point of `to` and self to use for the alignment.

    Returns:
        self
    """
    self_x, self_y = get_critical_point(self.geom, -1 * direction)
    target_x, target_y = get_critical_point(to.geom, direction)
    matrix = next_to(
        start=start,
        end=end,
        target_x=target_x,
        target_y=target_y,
        self_x=self_x,
        self_y=self_y,
        direction=direction,
        offset=offset,
        ease=easing,
        frame=self.frame,
    )
    return self.apply_transform(matrix)

lock_on2

lock_on2(target, reference=None, direction=ORIGIN, x=True, y=True)

Lock on to a target.

Parameters:

Name Type Description Default
target Transformable

Object to lock onto

required
reference ReactiveValue[GeometryT] | None

Measure from this object. This is useful for TextSelections, where you want to align to a particular character in the selection.

None
x bool

If true, lock on in the x dimension.

True
y bool

if true, lock on in the y dimension.

True
Source code in src/keyed/transforms.py
def lock_on2(
    self,
    target: Transformable,
    reference: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
    x: bool = True,
    y: bool = True,
) -> Self:
    """Lock on to a target.

    Args:
        target: Object to lock onto
        reference: Measure from this object. This is useful for TextSelections, where you want to align
            to a particular character in the selection.
        x: If true, lock on in the x dimension.
        y: if true, lock on in the y dimension.
    """
    reference_ = reference if reference is not None else self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference_,
            direction=direction,
            x=x,
            y=y,
        )
    )

is_visible

is_visible(obj)

Check if an object is visible.

Parameters:

Name Type Description Default
obj Any

Query object.

required

Returns:

Type Description
bool

True if the object is visible, False otherwise.

Note

Does not consider if an object is within the bounds of the canvas.

Source code in src/keyed/base.py
def is_visible(obj: Any) -> bool:
    """Check if an object is visible.

    Args:
        obj: Query object.

    Returns:
        True if the object is visible, False otherwise.

    Note:
        Does not consider if an object is within the bounds of the canvas.
    """
    return isinstance(obj, HasAlpha) and unref(obj.alpha) > 0

keyed.group

A class for manipulating groups of things.

Selection module-attribute

Selection = Group

Alias of Group.

Group

Bases: Transformable, list[T]

A plain container of drawable objects with batch transformations/animations.

Parameters:

Name Type Description Default
iterable Iterable[T]

An iterable of drawable objects.

tuple()
Source code in src/keyed/group.py
class Group(Transformable, list[T]):
    """A plain container of drawable objects with batch transformations/animations.

    Args:
        iterable: An iterable of drawable objects.
    """

    def __init__(self, iterable: Iterable[T] = tuple(), /) -> None:
        super().__init__(iterable)

    @property
    def scene(self) -> Scene:  # type: ignore[override]
        """Returns the scene associated with the first object in the group.

        Raises:
            ValueError: If the group is empty and the scene cannot be retrieved.
        """
        if not self:
            raise ValueError("Cannot retrieve 'scene': Selection is empty.")
        return self[0].scene

    @property
    def frame(self) -> Signal[int]:  # type: ignore[override]
        """Returns the frame associated with the first object in the group.

        Raises:
            ValueError: If the group is empty and the frame cannot be retrieved.
        """
        if not self:
            raise ValueError("Cannot retrieve 'frame': Selection is empty.")
        return self.scene.frame

    def _animate(self, property: str, animation: Animation) -> Self:
        """Animate a property across all objects in the group.

        Args:
            property: str
            animation: Animation

        Returns:
            None
        """
        for item in self:
            item._animate(property, animation)
        return self

    def draw(self) -> None:
        """Draws all objects in the group."""
        for item in self:
            item.draw()

    def set(self, property: str, value: Any, frame: int = 0) -> Self:
        """Set a property to a new value for all objects in the group at the specified frame.

        Args:
            property: The name of the property to set.
            value: The value to set it to.
            frame: The frame at which to set the value.

        Returns:
            Self

        See Also:
            [keyed.Group.set_literal][keyed.Group.set_literal]
        """
        for item in self:
            item.set(property, value, frame)
        return self

    def set_literal(self, property: str, value: Any) -> Self:
        """Overwrite a property to a new value for all objects in the group.

        Args:
            property: The name of the property to set.
            value: Value to set to.

        Returns:
            Self

        See Also:
            [keyed.Group.set][keyed.Group.set]
        """
        for item in self:
            item.set_literal(property, value)
        return self

    def center(self, frame: int = ALWAYS) -> Self:
        """Center the group within the scene."""
        self.align_to(self.scene, start=frame, end=frame, direction=ORIGIN, center_on_zero=True)
        return self

    def line_to(
        self,
        other: Transformable,
        self_direction: Direction = RIGHT,
        other_direction: Direction = LEFT,
        **line_kwargs: Any,
    ) -> Line:
        """Create a line connecting this group to another transformable object."""
        from .line import Line

        self_point = get_critical_point(self.geom, direction=self_direction)  # type: ignore[arg-type]
        other_point = get_critical_point(other.geom, direction=other_direction)
        return Line(self.scene, x0=self_point[0], y0=self_point[1], x1=other_point[0], y1=other_point[1], **line_kwargs)

    def emphasize(
        self,
        buffer: float = 5,
        radius: float = 0,
        fill_color: tuple[float, float, float] = (1, 1, 1),
        color: tuple[float, float, float] = (1, 1, 1),
        alpha: float = 1,
        dash: tuple[Sequence[float], float] | None = None,
        line_width: float = 2,
        draw_fill: bool = True,
        draw_stroke: bool = True,
        operator: cairo.Operator = cairo.OPERATOR_SCREEN,
    ):
        """Emphasize the group by drawing a rectangle around its geometry."""
        from .shapes import Rectangle

        return Rectangle(
            self.scene,
            color=color,
            x=self.center_x,
            y=self.center_y,
            width=self.width + buffer,
            height=self.height + buffer,
            fill_color=fill_color,
            alpha=alpha,
            dash=dash,
            operator=operator,
            line_width=line_width,
            draw_fill=draw_fill,
            draw_stroke=draw_stroke,
            radius=radius,
        )

    def write_on(
        self,
        property: str,
        animator: Callable,
        start: int,
        delay: int,
        duration: int,
    ) -> Self:
        """Sequentially animates a property across all objects in the group.

        Args:
            property: The property to animate.
            animator : The animation function to apply, which should create an Animation.
                See :func:`keyed.animations.stagger`.
            start: The frame at which the first animation should start.
            delay: The delay in frames before starting the next object's animation.
            duration: The duration of each object's animation in frames.
        """
        frame = start
        for item in self:
            animation = animator(start=frame, end=frame + duration)
            item._animate(property, animation)
            frame += delay
        return self

    @overload
    def __getitem__(self, key: SupportsIndex) -> T:
        pass

    @overload
    def __getitem__(self, key: slice) -> Self:
        pass

    def __getitem__(self, key: SupportsIndex | slice) -> T | Self:
        """Retrieve an item or slice of items from the group based on the given key."""
        if isinstance(key, slice):
            return type(self)(super().__getitem__(key))
        else:
            return super().__getitem__(key)

    @property
    def geom(self) -> Computed[shapely.GeometryCollection[shapely.geometry.base.BaseGeometry]]:  # pyright: ignore[reportIncompatibleMethodOverride]
        """Return a reactive value of the geometry.

        Returns:
            A reactive value of the geometry.
        """

        @computed
        def f(geoms: list[shapely.geometry.base.BaseGeometry]) -> shapely.GeometryCollection:
            return shapely.GeometryCollection([unref(geom) for geom in geoms])

        return f([obj.geom for obj in self])

    @property
    def geom_now(self) -> shapely.GeometryCollection:
        return shapely.GeometryCollection([obj.geom_now for obj in self])

    def apply_transform(self, matrix: ReactiveValue[cairo.Matrix]) -> Self:
        # TODO should we allow transform by HasValue[cairo.Matrix]? Probably...
        for obj in self:
            obj.apply_transform(matrix)
        return self

    @property
    def dependencies(self) -> list[Variable]:
        out: list[Variable] = []
        for obj in self:
            out.extend(obj.dependencies)
        return out

    def cleanup(self) -> None:
        for obj in self:
            obj.cleanup()

    def fade(self, value: HasValue[float], start: int, end: int, ease: EasingFunctionT = linear_in_out) -> Self:
        for obj in self:
            obj.fade(value, start, end, ease)
        return self

    def distribute(
        self,
        direction: Direction = ORIGIN,
        start: int = ALWAYS,
        end: int = ALWAYS,
        easing: EasingFunctionT = cubic_in_out,
        x: bool = True,
        y: bool = True,
    ) -> Self:
        """Distribute objects evenly between the first and last objects in the group.

        This keeps the first and last objects in their initial positions and distributes
        the remaining objects in between with equal spacing.

        Args:
            direction: Direction used to get anchor points on objects
            start: Starting frame for the animation
            end: Ending frame for the animation
            easing: Easing function to use
            x: Whether to distribute along the x-axis
            y: Whether to distribute along the y-axis

        Returns:
            self
        """
        objects = list(self)
        if len(objects) <= 2:
            # No distribution needed for 0, 1, or 2 objects
            return self

        # Get the first and last objects
        first, *middle, last = objects

        # Get positions of the first and last objects using the specified direction
        first_x, first_y = get_critical_point(first.geom, direction)
        last_x, last_y = get_critical_point(last.geom, -1 * direction)

        # Use these positions as the distribution bounds
        start_x, end_x = first_x, last_x
        start_y, end_y = first_y, last_y

        # Position each middle object
        for i, obj in enumerate(middle, 1):
            # Calculate interpolation factor (fraction of position in the sequence)
            t = i / (len(objects) - 1)

            # Get current position of this object
            obj_x, obj_y = get_critical_point(obj.geom, direction)

            # Calculate target position and translation
            dx = (start_x + t * (end_x - start_x) - obj_x) if x else 0
            dy = (start_y + t * (end_y - start_y) - obj_y) if y else 0

            # Apply transformation
            obj.translate(x=dx, y=dy, start=start, end=end, easing=easing)

        return self

scene property

scene

Returns the scene associated with the first object in the group.

Raises:

Type Description
ValueError

If the group is empty and the scene cannot be retrieved.

frame property

frame

Returns the frame associated with the first object in the group.

Raises:

Type Description
ValueError

If the group is empty and the frame cannot be retrieved.

geom property

geom

Return a reactive value of the geometry.

Returns:

Type Description
Computed[GeometryCollection[BaseGeometry]]

A reactive value of the geometry.

draw

draw()

Draws all objects in the group.

Source code in src/keyed/group.py
def draw(self) -> None:
    """Draws all objects in the group."""
    for item in self:
        item.draw()

set

set(property, value, frame=0)

Set a property to a new value for all objects in the group at the specified frame.

Parameters:

Name Type Description Default
property str

The name of the property to set.

required
value Any

The value to set it to.

required
frame int

The frame at which to set the value.

0

Returns:

Type Description
Self

Self

See Also

[keyed.Group.set_literal][]

Source code in src/keyed/group.py
def set(self, property: str, value: Any, frame: int = 0) -> Self:
    """Set a property to a new value for all objects in the group at the specified frame.

    Args:
        property: The name of the property to set.
        value: The value to set it to.
        frame: The frame at which to set the value.

    Returns:
        Self

    See Also:
        [keyed.Group.set_literal][keyed.Group.set_literal]
    """
    for item in self:
        item.set(property, value, frame)
    return self

set_literal

set_literal(property, value)

Overwrite a property to a new value for all objects in the group.

Parameters:

Name Type Description Default
property str

The name of the property to set.

required
value Any

Value to set to.

required

Returns:

Type Description
Self

Self

See Also

[keyed.Group.set][]

Source code in src/keyed/group.py
def set_literal(self, property: str, value: Any) -> Self:
    """Overwrite a property to a new value for all objects in the group.

    Args:
        property: The name of the property to set.
        value: Value to set to.

    Returns:
        Self

    See Also:
        [keyed.Group.set][keyed.Group.set]
    """
    for item in self:
        item.set_literal(property, value)
    return self

center

center(frame=ALWAYS)

Center the group within the scene.

Source code in src/keyed/group.py
def center(self, frame: int = ALWAYS) -> Self:
    """Center the group within the scene."""
    self.align_to(self.scene, start=frame, end=frame, direction=ORIGIN, center_on_zero=True)
    return self

line_to

line_to(other, self_direction=RIGHT, other_direction=LEFT, **line_kwargs)

Create a line connecting this group to another transformable object.

Source code in src/keyed/group.py
def line_to(
    self,
    other: Transformable,
    self_direction: Direction = RIGHT,
    other_direction: Direction = LEFT,
    **line_kwargs: Any,
) -> Line:
    """Create a line connecting this group to another transformable object."""
    from .line import Line

    self_point = get_critical_point(self.geom, direction=self_direction)  # type: ignore[arg-type]
    other_point = get_critical_point(other.geom, direction=other_direction)
    return Line(self.scene, x0=self_point[0], y0=self_point[1], x1=other_point[0], y1=other_point[1], **line_kwargs)

emphasize

emphasize(buffer=5, radius=0, fill_color=(1, 1, 1), color=(1, 1, 1), alpha=1, dash=None, line_width=2, draw_fill=True, draw_stroke=True, operator=OPERATOR_SCREEN)

Emphasize the group by drawing a rectangle around its geometry.

Source code in src/keyed/group.py
def emphasize(
    self,
    buffer: float = 5,
    radius: float = 0,
    fill_color: tuple[float, float, float] = (1, 1, 1),
    color: tuple[float, float, float] = (1, 1, 1),
    alpha: float = 1,
    dash: tuple[Sequence[float], float] | None = None,
    line_width: float = 2,
    draw_fill: bool = True,
    draw_stroke: bool = True,
    operator: cairo.Operator = cairo.OPERATOR_SCREEN,
):
    """Emphasize the group by drawing a rectangle around its geometry."""
    from .shapes import Rectangle

    return Rectangle(
        self.scene,
        color=color,
        x=self.center_x,
        y=self.center_y,
        width=self.width + buffer,
        height=self.height + buffer,
        fill_color=fill_color,
        alpha=alpha,
        dash=dash,
        operator=operator,
        line_width=line_width,
        draw_fill=draw_fill,
        draw_stroke=draw_stroke,
        radius=radius,
    )

write_on

write_on(property, animator, start, delay, duration)

Sequentially animates a property across all objects in the group.

Parameters:

Name Type Description Default
property str

The property to animate.

required
animator

The animation function to apply, which should create an Animation. See :func:keyed.animations.stagger.

required
start int

The frame at which the first animation should start.

required
delay int

The delay in frames before starting the next object's animation.

required
duration int

The duration of each object's animation in frames.

required
Source code in src/keyed/group.py
def write_on(
    self,
    property: str,
    animator: Callable,
    start: int,
    delay: int,
    duration: int,
) -> Self:
    """Sequentially animates a property across all objects in the group.

    Args:
        property: The property to animate.
        animator : The animation function to apply, which should create an Animation.
            See :func:`keyed.animations.stagger`.
        start: The frame at which the first animation should start.
        delay: The delay in frames before starting the next object's animation.
        duration: The duration of each object's animation in frames.
    """
    frame = start
    for item in self:
        animation = animator(start=frame, end=frame + duration)
        item._animate(property, animation)
        frame += delay
    return self

distribute

distribute(direction=ORIGIN, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, x=True, y=True)

Distribute objects evenly between the first and last objects in the group.

This keeps the first and last objects in their initial positions and distributes the remaining objects in between with equal spacing.

Parameters:

Name Type Description Default
direction Direction

Direction used to get anchor points on objects

ORIGIN
start int

Starting frame for the animation

ALWAYS
end int

Ending frame for the animation

ALWAYS
easing EasingFunctionT

Easing function to use

cubic_in_out
x bool

Whether to distribute along the x-axis

True
y bool

Whether to distribute along the y-axis

True

Returns:

Type Description
Self

self

Source code in src/keyed/group.py
def distribute(
    self,
    direction: Direction = ORIGIN,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    x: bool = True,
    y: bool = True,
) -> Self:
    """Distribute objects evenly between the first and last objects in the group.

    This keeps the first and last objects in their initial positions and distributes
    the remaining objects in between with equal spacing.

    Args:
        direction: Direction used to get anchor points on objects
        start: Starting frame for the animation
        end: Ending frame for the animation
        easing: Easing function to use
        x: Whether to distribute along the x-axis
        y: Whether to distribute along the y-axis

    Returns:
        self
    """
    objects = list(self)
    if len(objects) <= 2:
        # No distribution needed for 0, 1, or 2 objects
        return self

    # Get the first and last objects
    first, *middle, last = objects

    # Get positions of the first and last objects using the specified direction
    first_x, first_y = get_critical_point(first.geom, direction)
    last_x, last_y = get_critical_point(last.geom, -1 * direction)

    # Use these positions as the distribution bounds
    start_x, end_x = first_x, last_x
    start_y, end_y = first_y, last_y

    # Position each middle object
    for i, obj in enumerate(middle, 1):
        # Calculate interpolation factor (fraction of position in the sequence)
        t = i / (len(objects) - 1)

        # Get current position of this object
        obj_x, obj_y = get_critical_point(obj.geom, direction)

        # Calculate target position and translation
        dx = (start_x + t * (end_x - start_x) - obj_x) if x else 0
        dy = (start_y + t * (end_y - start_y) - obj_y) if y else 0

        # Apply transformation
        obj.translate(x=dx, y=dy, start=start, end=end, easing=easing)

    return self

rotate

rotate(amount, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Rotate the object.

Parameters:

Name Type Description Default
amount HasValue[float]

Amount to rotate by.

required
start int

The frame to start rotating.

ALWAYS
end int

The frame to end rotating.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to rotate.

None
direction Direction

The relative critical point of the center.

ORIGIN

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def rotate(
    self,
    amount: HasValue[float],
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Rotate the object.

    Args:
        amount: Amount to rotate by.
        start: The frame to start rotating.
        end: The frame to end rotating.
        easing: The easing function to use.
        center: The object around which to rotate.
        direction: The relative critical point of the center.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    return self.apply_transform(rotate(start, end, amount, cx, cy, self.frame, easing))

scale

scale(amount, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Scale the object.

Parameters:

Name Type Description Default
amount HasValue[float]

Amount to scale by.

required
start int

The frame to start scaling.

ALWAYS
end int

The frame to end scaling.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to rotate.

None
direction Direction

The relative critical point of the center.

ORIGIN

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def scale(
    self,
    amount: HasValue[float],
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Scale the object.

    Args:
        amount: Amount to scale by.
        start: The frame to start scaling.
        end: The frame to end scaling.
        easing: The easing function to use.
        center: The object around which to rotate.
        direction: The relative critical point of the center.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    return self.apply_transform(scale(start, end, amount, cx, cy, self.frame, easing))

translate

translate(x=0, y=0, start=ALWAYS, end=ALWAYS, easing=cubic_in_out)

Translate the object.

Parameters:

Name Type Description Default
x HasValue[float]

x offset.

0
y HasValue[float]

y offset.

0
start int

The frame to start translating.

ALWAYS
end int

The frame to end translating.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
Source code in src/keyed/transforms.py
def translate(
    self,
    x: HasValue[float] = 0,
    y: HasValue[float] = 0,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
) -> Self:
    """Translate the object.

    Args:
        x: x offset.
        y: y offset.
        start: The frame to start translating.
        end: The frame to end translating.
        easing: The easing function to use.
    """
    return self.apply_transform(translate(start, end, x, y, self.frame, easing))

move_to

move_to(x=None, y=None, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Move object to absolute coordinates.

Parameters:

Name Type Description Default
x HasValue[float] | None

Destination x coordinate

None
y HasValue[float] | None

Destination y coordinate

None
start int

Starting frame, by default ALWAYS

ALWAYS
end int

Ending frame, by default ALWAYS

ALWAYS
easing EasingFunctionT

Easing function, by default cubic_in_out

cubic_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/transforms.py
def move_to(
    self,
    x: HasValue[float] | None = None,
    y: HasValue[float] | None = None,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Move object to absolute coordinates.

    Args:
        x: Destination x coordinate
        y: Destination y coordinate
        start: Starting frame, by default ALWAYS
        end: Ending frame, by default ALWAYS
        easing: Easing function, by default cubic_in_out

    Returns:
        Self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    return self.apply_transform(
        move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing)
    )

align_to

align_to(to, start=ALWAYS, lock=None, end=ALWAYS, from_=None, easing=cubic_in_out, direction=ORIGIN, center_on_zero=False)

Align the object to another object.

Parameters:

Name Type Description Default
to Transformable

The object to align to.

required
start int

Start of animation (begin aligning to the object).

ALWAYS
end int

End of animation (finish aligning to the object at this frame, and then stay there).

ALWAYS
from_ HasValue[GeometryT] | None

Use this object as self when doing the alignment. This is helpful for code animations. It is sometimes desirable to align, say, the top-left edge of one character in a TextSelection to the top-left of another character.

None
easing EasingFunctionT

The easing function to use.

cubic_in_out
direction Direction

The critical point of to and from_to use for the alignment.

ORIGIN
center_on_zero bool

If true, align along the "0"-valued dimensions. Otherwise, only align to on non-zero directions. This is beneficial for, say, centering the object at the origin (which has a vector that consists of two zeros).

False

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def align_to(
    self,
    to: Transformable,
    start: int = ALWAYS,
    lock: int | None = None,
    end: int = ALWAYS,
    from_: HasValue[GeometryT] | None = None,
    easing: EasingFunctionT = cubic_in_out,
    direction: Direction = ORIGIN,
    center_on_zero: bool = False,
) -> Self:
    """Align the object to another object.

    Args:
        to: The object to align to.
        start: Start of animation (begin aligning to the object).
        end: End of animation (finish aligning to the object at this frame, and then stay there).
        from_: Use this object as self when doing the alignment. This is helpful for code
            animations. It is sometimes desirable to align, say, the top-left edge of one
            character in a TextSelection to the top-left of another character.
        easing: The easing function to use.
        direction: The critical point of to and from_to use for the alignment.
        center_on_zero: If true, align along the "0"-valued dimensions. Otherwise, only align to on non-zero
            directions. This is beneficial for, say, centering the object at the origin (which has
            a vector that consists of two zeros).

    Returns:
        self
    """
    # TODO: I'd like to get rid of center_on_zero.
    to_geom = to.geom
    from_geom = from_ if from_ is not None else self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to_geom,
            from_geom,
            frame=self.frame,
            start=start,
            lock=lock,
            end=end,
            ease=easing,
            direction=direction,
            center_on_zero=center_on_zero,
        )
    )

lock_on

lock_on(target, reference=None, start=ALWAYS, end=-ALWAYS, direction=ORIGIN, x=True, y=True)

Lock on to a target.

Parameters:

Name Type Description Default
target Transformable

Object to lock onto

required
reference ReactiveValue[GeometryT] | None

Measure from this object. This is useful for TextSelections, where you want to align to a particular character in the selection.

None
start int

When to start locking on.

ALWAYS
end int

When to end locking on.

-ALWAYS
x bool

If true, lock on in the x dimension.

True
y bool

If true, lock on in the y dimension.

True
Source code in src/keyed/transforms.py
def lock_on(
    self,
    target: Transformable,
    reference: ReactiveValue[GeometryT] | None = None,
    start: int = ALWAYS,
    end: int = -ALWAYS,
    direction: Direction = ORIGIN,
    x: bool = True,
    y: bool = True,
) -> Self:
    """Lock on to a target.

    Args:
        target: Object to lock onto
        reference: Measure from this object. This is useful for TextSelections, where you want to align
            to a particular character in the selection.
        start: When to start locking on.
        end: When to end locking on.
        x: If true, lock on in the x dimension.
        y: If true, lock on in the y dimension.
    """
    reference_ = reference if reference is not None else self.geom
    return self.apply_transform(
        lock_on(
            target=target.geom,
            reference=reference_,
            frame=self.frame,
            start=start,
            end=end,
            direction=direction,
            x=x,
            y=y,
        )
    )

shear

shear(angle_x=0, angle_y=0, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None)

Shear the object.

Parameters:

Name Type Description Default
angle_x HasValue[float]

Angle (in degrees) to shear by along x direction.

0
angle_y HasValue[float]

Angle (in degrees) to shear by along x direction.

0
start int

The frame to start scaling.

ALWAYS
end int

The frame to end scaling.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to rotate.

None

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def shear(
    self,
    angle_x: HasValue[float] = 0,
    angle_y: HasValue[float] = 0,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
) -> Self:
    """Shear the object.

    Args:
        angle_x: Angle (in degrees) to shear by along x direction.
        angle_y: Angle (in degrees) to shear by along x direction.
        start: The frame to start scaling.
        end: The frame to end scaling.
        easing: The easing function to use.
        center: The object around which to rotate.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, ORIGIN)
    return self.apply_transform(
        shear(
            start=start,
            end=end,
            angle_x=angle_x,
            angle_y=angle_y,
            cx=cx,
            cy=cy,
            frame=self.frame,
            ease=easing,
        )
    )

stretch

stretch(scale_x=1, scale_y=1, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Stretch the object.

Parameters:

Name Type Description Default
scale_x HasValue[float]

Amount to scale by in x direction.

1
scale_y HasValue[float]

Amount to scale by in y direction.

1
start int

The frame to start scaling.

ALWAYS
end int

The frame to end scaling.

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to rotate.

None
direction Direction

The relative critical point of the center.

ORIGIN

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def stretch(
    self,
    scale_x: HasValue[float] = 1,
    scale_y: HasValue[float] = 1,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Stretch the object.

    Args:
        scale_x: Amount to scale by in x direction.
        scale_y: Amount to scale by in y direction.
        start: The frame to start scaling.
        end: The frame to end scaling.
        easing: The easing function to use.
        center: The object around which to rotate.
        direction: The relative critical point of the center.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    return self.apply_transform(
        stretch(
            start=start,
            end=end,
            scale_x=scale_x,
            scale_y=scale_y,
            cx=cx,
            cy=cy,
            frame=self.frame,
            ease=easing,
        )
    )

match_size

match_size(other, match_x=True, match_y=True, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, center=None, direction=ORIGIN)

Scale object dimensions to match another object.

Parameters:

Name Type Description Default
other Transformable

Object whose width and height to match.

required
match_x bool

If True, match width.

True
match_y bool

If True, match height.

True
start int

Frame at which scaling begins.

ALWAYS
end int

Frame at which scaling stops varying.

ALWAYS
easing EasingFunctionT

Easing function to use.

cubic_in_out
center ReactiveValue[GeometryT] | None

The object around which to scale.

None
direction Direction

The relative critical point of the center.

ORIGIN

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def match_size(
    self,
    other: Transformable,
    match_x: bool = True,
    match_y: bool = True,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    center: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
) -> Self:
    """Scale object dimensions to match another object.

    Args:
        other: Object whose width and height to match.
        match_x: If True, match width.
        match_y: If True, match height.
        start: Frame at which scaling begins.
        end: Frame at which scaling stops varying.
        easing: Easing function to use.
        center: The object around which to scale.
        direction: The relative critical point of the center.

    Returns:
        self
    """
    center_ = center if center is not None else self.geom
    cx, cy = get_critical_point(center_, direction)
    matrix = match_size(
        start=start,
        end=end,
        match_x=match_x,
        match_y=match_y,
        target_width=other.width,
        target_height=other.height,
        original_width=self.width,
        original_height=self.height,
        cx=cx,
        cy=cy,
        frame=self.frame,
        ease=easing,
    )
    return self.apply_transform(matrix)

next_to

next_to(to, start=ALWAYS, end=ALWAYS, easing=cubic_in_out, offset=10.0, direction=LEFT)

Align the object to another object.

Parameters:

Name Type Description Default
to Transformable

The object to align to.

required
start int

Start of animation (begin aligning to the object).

ALWAYS
end int

End of animation (finish aligning to the object at this frame, and then stay there).

ALWAYS
easing EasingFunctionT

The easing function to use.

cubic_in_out
offset HasValue[float]

Distance between objects (in pixels).

10.0
direction Direction

The critical point of to and self to use for the alignment.

LEFT

Returns:

Type Description
Self

self

Source code in src/keyed/transforms.py
def next_to(
    self,
    to: Transformable,
    start: int = ALWAYS,
    end: int = ALWAYS,
    easing: EasingFunctionT = cubic_in_out,
    offset: HasValue[float] = 10.0,
    direction: Direction = LEFT,
) -> Self:
    """Align the object to another object.

    Args:
        to: The object to align to.
        start: Start of animation (begin aligning to the object).
        end: End of animation (finish aligning to the object at this frame, and then stay there).
        easing: The easing function to use.
        offset: Distance between objects (in pixels).
        direction: The critical point of `to` and self to use for the alignment.

    Returns:
        self
    """
    self_x, self_y = get_critical_point(self.geom, -1 * direction)
    target_x, target_y = get_critical_point(to.geom, direction)
    matrix = next_to(
        start=start,
        end=end,
        target_x=target_x,
        target_y=target_y,
        self_x=self_x,
        self_y=self_y,
        direction=direction,
        offset=offset,
        ease=easing,
        frame=self.frame,
    )
    return self.apply_transform(matrix)

lock_on2

lock_on2(target, reference=None, direction=ORIGIN, x=True, y=True)

Lock on to a target.

Parameters:

Name Type Description Default
target Transformable

Object to lock onto

required
reference ReactiveValue[GeometryT] | None

Measure from this object. This is useful for TextSelections, where you want to align to a particular character in the selection.

None
x bool

If true, lock on in the x dimension.

True
y bool

if true, lock on in the y dimension.

True
Source code in src/keyed/transforms.py
def lock_on2(
    self,
    target: Transformable,
    reference: ReactiveValue[GeometryT] | None = None,
    direction: Direction = ORIGIN,
    x: bool = True,
    y: bool = True,
) -> Self:
    """Lock on to a target.

    Args:
        target: Object to lock onto
        reference: Measure from this object. This is useful for TextSelections, where you want to align
            to a particular character in the selection.
        x: If true, lock on in the x dimension.
        y: if true, lock on in the y dimension.
    """
    reference_ = reference if reference is not None else self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference_,
            direction=direction,
            x=x,
            y=y,
        )
    )