Skip to content

Shapes

keyed.shapes

Drawable primative shapes, like rectangles and circles.

Shape

Bases: Base

Base class for drawable shapes that can be added to a scene.

Subclasses should provide specific drawing behavior.

Attributes:

Name Type Description
scene Scene

The scene to which the shape belongs.

ctx ContextT

The Cairo context used for drawing the shape.

color HasValue[Color]

The color of the shape's stroke.

fill_color HasValue[Color]

The color used to fill the shape.

alpha ReactiveValue[float]

The opacity of the shape, where 0 is transparent and 1 is opaque.

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

The dash pattern for the shape's outline. A tuple where the first element is a sequence of dash lengths and the second element is the offset. None indicates a solid line.

operator Operator

The compositing operator used when rendering the shape.

draw_fill bool

Whether the shape should be filled.

draw_stroke bool

Whether the shape's outline should be drawn.

line_width ReactiveValue[float]

The width of the outline stroke.

line_cap LineCap

The style of the line ends among cairo.LINE_CAP_BUTT, cairo.LINE_CAP_ROUND, or cairo.LINE_CAP_SQUARE.

line_join LineJoin

Specifies how the joins between line segments are drawn among cairo.LINE_JOIN_MITER, cairo.LINE_JOIN_ROUND, or cairo.LINE_JOIN_BEVEL.

Source code in src/keyed/shapes.py
class Shape(Base):
    """Base class for drawable shapes that can be added to a scene.

    Subclasses should provide specific drawing behavior.

    Attributes:
        scene: The scene to which the shape belongs.
        ctx: The Cairo context used for drawing the shape.
        color: The color of the shape's stroke.
        fill_color: The color used to fill the shape.
        alpha: The opacity of the shape, where 0 is transparent and 1 is opaque.
        dash: The dash pattern for the shape's outline. A tuple where the first element
            is a sequence of dash lengths and the second element is the offset. None
            indicates a solid line.
        operator: The compositing operator used when rendering the shape.
        draw_fill: Whether the shape should be filled.
        draw_stroke: Whether the shape's outline should be drawn.
        line_width: The width of the outline stroke.
        line_cap: The style of the line ends among cairo.LINE_CAP_BUTT, cairo.LINE_CAP_ROUND,
            or cairo.LINE_CAP_SQUARE.
        line_join: Specifies how the joins between line segments are drawn among cairo.LINE_JOIN_MITER,
            cairo.LINE_JOIN_ROUND, or cairo.LINE_JOIN_BEVEL.
    """

    scene: Scene
    ctx: ContextT
    color: HasValue[Color]
    fill_color: HasValue[Color]
    alpha: ReactiveValue[float]
    dash: tuple[Sequence[float], float] | None
    operator: cairo.Operator = cairo.OPERATOR_OVER
    draw_fill: bool
    draw_stroke: bool
    line_width: ReactiveValue[float]
    line_cap: cairo.LineCap
    line_join: cairo.LineJoin
    fill_pattern: cairo.Pattern | None
    stroke_pattern: cairo.Pattern | None

    def __init__(self, scene: Scene) -> None:
        super().__init__(scene)
        self.fill_pattern: cairo.Pattern | None = None
        self.stroke_pattern: cairo.Pattern | None = None

    def _apply_fill(self, ctx: cairo.Context) -> None:
        if self.fill_pattern:
            ctx.set_source(self.fill_pattern)
        else:
            ctx.set_source_rgba(*unref(self.fill_color).rgb, unref(self.alpha) if self._direct_mode else 1)

    def _apply_stroke(self, ctx: cairo.Context) -> None:
        if self.stroke_pattern:
            ctx.set_source(self.stroke_pattern)
        else:
            ctx.set_source_rgba(*unref(self.color).rgb, unref(self.alpha) if self._direct_mode else 1)

    def _draw_shape(self) -> None:
        """Draw the specific shape on the canvas.

        This method must be implemented by each subclass to define how the shape is drawn.
        """
        pass

    @contextmanager
    def _style(self) -> Generator[None, None, None]:
        """Context manager for setting up the drawing style for the shape.

        Temporarily sets various drawing properties such as line width, line cap, line join,
        dash pattern, and operator based on the shape's attributes.

        Yields:
            None: Yields control back to the caller within the context of the configured style.
        """
        try:
            self.ctx.save()
            if self.dash is not None:
                self.ctx.set_dash(*self.dash)
            self.ctx.set_operator(self.operator)
            self.ctx.set_line_width(self.line_width.value)
            self.ctx.set_line_cap(self.line_cap)
            self.ctx.set_line_join(self.line_join)
            yield
        finally:
            self.ctx.restore()

    def _draw_direct(self) -> None:
        with self._style():
            self.ctx.save()
            self.ctx.transform(self.controls.matrix.value)
            self._draw_shape()

            if self.draw_fill:
                self._apply_fill(self.ctx)
                if self.draw_stroke:
                    self.ctx.fill_preserve()
                else:
                    self.ctx.fill()
            if self.draw_stroke:
                self._apply_stroke(self.ctx)
                self.ctx.stroke()
            self.ctx.restore()

    def _draw(self) -> None:
        """Draw the shape within its styled context, applying transformations."""
        with self._style():
            self.ctx.save()
            self.ctx.transform(self.controls.matrix.value)

            # Create a group for storing both the fill and stroke drawing operations
            self.ctx.push_group()

            # Use OVER for drawing within the group. We'll apply self.operator later when
            # writing to the canvas.
            self.ctx.set_operator(cairo.OPERATOR_OVER)

            # Create the geometry of the shape
            self._draw_shape()

            # Fill and/or stroke to the group
            if self.draw_fill:
                self._apply_fill(self.ctx)
                if self.draw_stroke:
                    self.ctx.fill_preserve()
                else:
                    self.ctx.fill()

            if self.draw_stroke:
                self._apply_stroke(self.ctx)
                self.ctx.stroke()

            # Paint the group with the operator to the canvas
            self.ctx.pop_group_to_source()
            self.ctx.set_operator(self.operator)
            self.ctx.paint_with_alpha(unref(self.alpha))

            self.ctx.restore()

    @property
    def _direct_mode(self) -> bool:
        problematic_modes = {
            cairo.OPERATOR_CLEAR,
            cairo.OPERATOR_SOURCE,
            cairo.OPERATOR_DEST,
            # cairo.OPERATOR_IN,
            # cairo.OPERATOR_OUT,
            # cairo.OPERATOR_DEST_IN,
            # cairo.OPERATOR_DEST_ATOP
        }

        return self.operator in problematic_modes

    def draw(self) -> None:
        """Draw the shape with a simplified approach for all blend modes."""
        if self._direct_mode:
            self._draw_direct()
        else:
            self._draw()

    def cleanup(self) -> None:
        if isinstance(self.ctx, Cleanable):
            self.ctx.cleanup()

geom property

geom

Return a reactive value of the transformed geometry.

draw

draw()

Draw the shape with a simplified approach for all blend modes.

Source code in src/keyed/shapes.py
def draw(self) -> None:
    """Draw the shape with a simplified approach for all blend modes."""
    if self._direct_mode:
        self._draw_direct()
    else:
        self._draw()

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)
    self.apply_transform(move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing))
    return self

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_ ReactiveValue[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_: ReactiveValue[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.
    from_ = from_ or self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to.geom,
            from_,
            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 or 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,
        )
    )

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 or self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference,
            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,
        )
    )

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 from_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 from_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)

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)

Rectangle

Bases: Shape

A rectangle with optionally rounded corners.

Parameters:

Name Type Description Default
scene Scene

The scene to which the rectangle belongs.

required
width HasValue[float]

The width of the rectangle.

10
height HasValue[float]

The height of the rectangle.

10
x HasValue[float] | None

The x-coordinate of the rectangle's position. Default is to center in the scene.

None
y HasValue[float] | None

The y-coordinate of the rectangle's position. Default is to center in the scene.

None
radius HasValue[float]

The radius of the corners of the rectangle.

0
color tuple[float, float, float] | HasValue[Color]

The color of the rectangle's border.

(1, 1, 1)
fill_color tuple[float, float, float] | HasValue[Color]

The fill color of the rectangle.

(1, 1, 1)
alpha HasValue[float]

The opacity level of the rectangle.

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

The dash pattern for the outline of the rectangle.

None
operator Operator

The compositing operator to use for drawing.

OPERATOR_OVER
draw_fill bool

Whether to fill the rectangle.

True
draw_stroke bool

Whether to draw the stroke of the rectangle.

True
line_width HasValue[float]

The width of the line used to stroke the rectangle.

2
rotation HasValue[float]

The rotation angle of the rectangle, in radians.

0
round_tl bool

Whether to round the top-left corner.

True
round_tr bool

Whether to round the top-right corner.

True
round_br bool

Whether to round the bottom-right corner.

True
round_bl bool

Whether to round the bottom-left corner.

True
Source code in src/keyed/shapes.py
class Rectangle(Shape):
    """A rectangle with optionally rounded corners.

    Args:
        scene: The scene to which the rectangle belongs.
        width: The width of the rectangle.
        height: The height of the rectangle.
        x: The x-coordinate of the rectangle's position. Default is to center in the scene.
        y: The y-coordinate of the rectangle's position. Default is to center in the scene.
        radius: The radius of the corners of the rectangle.
        color: The color of the rectangle's border.
        fill_color: The fill color of the rectangle.
        alpha: The opacity level of the rectangle.
        dash: The dash pattern for the outline of the rectangle.
        operator: The compositing operator to use for drawing.
        draw_fill: Whether to fill the rectangle.
        draw_stroke: Whether to draw the stroke of the rectangle.
        line_width: The width of the line used to stroke the rectangle.
        rotation: The rotation angle of the rectangle, in radians.
        round_tl: Whether to round the top-left corner.
        round_tr: Whether to round the top-right corner.
        round_br: Whether to round the bottom-right corner.
        round_bl: Whether to round the bottom-left corner.
    """

    def __init__(
        self,
        scene: Scene,
        width: HasValue[float] = 10,
        height: HasValue[float] = 10,
        x: HasValue[float] | None = None,
        y: HasValue[float] | None = None,
        radius: HasValue[float] = 0,
        color: tuple[float, float, float] | HasValue[Color] = (1, 1, 1),
        fill_color: tuple[float, float, float] | HasValue[Color] = (1, 1, 1),
        alpha: HasValue[float] = 1,
        dash: tuple[Sequence[float], float] | None = None,
        operator: cairo.Operator = cairo.OPERATOR_OVER,
        draw_fill: bool = True,
        draw_stroke: bool = True,
        line_width: HasValue[float] = 2,
        rotation: HasValue[float] = 0,
        round_tl: bool = True,
        round_tr: bool = True,
        round_br: bool = True,
        round_bl: bool = True,
    ) -> None:
        super().__init__(scene)
        self.scene = scene
        self.ctx = scene.get_context()
        self.x = x if x is not None else scene.nx(0.5)
        self.y = y if y is not None else scene.ny(0.5)
        self.controls.delta_x.value = self.x
        self.controls.delta_y.value = self.y
        self.controls.rotation.value = rotation
        self._width = as_signal(width)
        self._height = as_signal(height)
        self.radius = as_signal(radius)
        self.alpha = as_signal(alpha)
        self.color = as_color(color)
        self.fill_color = as_color(fill_color)
        self.dash = dash
        self.operator = operator
        self.draw_fill = draw_fill
        self.draw_stroke = draw_stroke
        self.line_width = as_signal(line_width)
        self.line_cap = cairo.LINE_CAP_ROUND
        self.line_join = cairo.LINE_JOIN_ROUND
        self.round_tl = round_tl
        self.round_tr = round_tr
        self.round_br = round_br
        self.round_bl = round_bl
        self._dependencies.extend([self._width, self._height, self.radius])
        assert isinstance(self.controls.matrix, Signal)
        self.controls.matrix.value = self.controls.base_matrix()

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}("
            f"x={self.x}, "
            f"y={self.y}, "
            f"width={self._width}, "
            f"height={self._height}, "
            f"radius={self.radius}, "
            f"dash={self.dash}, "
            f"rotation={self.controls.rotation}, "
            ")"
        )

    def _draw_shape(self) -> None:
        """Draw the rectangle."""
        w = self._width.value
        h = self._height.value
        r = self.radius.value

        # Calculate the corners relative to center
        left = -w / 2
        right = w / 2
        top = -h / 2
        bottom = h / 2

        # Start at the top-middle if we're rounding the top-left corner
        if self.round_tl and r > 0:
            start_x = left + r
        else:
            start_x = left
        self.ctx.move_to(start_x, top)

        # Top-right corner
        if self.round_tr and r > 0:
            self.ctx.line_to(right - r, top)
            self.ctx.arc(right - r, top + r, r, -math.pi / 2, 0)
        else:
            self.ctx.line_to(right, top)

        # Bottom-right corner
        if self.round_br and r > 0:
            self.ctx.line_to(right, bottom - r)
            self.ctx.arc(right - r, bottom - r, r, 0, math.pi / 2)
        else:
            self.ctx.line_to(right, bottom)

        # Bottom-left corner
        if self.round_bl and r > 0:
            self.ctx.line_to(left + r, bottom)
            self.ctx.arc(left + r, bottom - r, r, math.pi / 2, math.pi)
        else:
            self.ctx.line_to(left, bottom)

        # Top-left corner
        if self.round_tl and r > 0:
            self.ctx.line_to(left, top + r)
            self.ctx.arc(left + r, top + r, r, math.pi, 3 * math.pi / 2)
        else:
            self.ctx.line_to(left, top)

        # Close the path
        self.ctx.close_path()

    @property
    def _raw_geom_now(self) -> BaseGeometry:
        """Return the geometric shape before any transformations.

        Returns:
            The polygon representing the rectangle.
        """
        width: float = self._width.value
        height: float = self._height.value
        radius: float = self.radius.value

        # Create a basic rectangle centered at origin
        left = -width / 2
        right = width / 2
        top = -height / 2
        bottom = height / 2
        rect = shapely.geometry.box(left, top, right, bottom)

        if radius == 0 or not any([self.round_tl, self.round_tr, self.round_br, self.round_bl]):
            # No rounding needed
            return rect

        # Subtract corner squares where rounding is required
        corners = {
            "tl": shapely.geometry.Point(left + radius, top + radius),
            "tr": shapely.geometry.Point(right - radius, top + radius),
            "br": shapely.geometry.Point(right - radius, bottom - radius),
            "bl": shapely.geometry.Point(left + radius, bottom - radius),
        }

        # Buffer the corners that need to be rounded
        for corner, point in corners.items():
            if getattr(self, f"round_{corner}"):
                circle = point.buffer(radius, resolution=16)
                square = shapely.geometry.box(point.x - radius, point.y - radius, point.x + radius, point.y + radius)
                rect = rect.difference(square).union(circle)

        return rect

    def clone(self) -> Self:
        new_obj = self.__class__.__new__(self.__class__)
        new_obj.__dict__.update(self.__dict__)

        new_obj.controls = TransformControls(new_obj)
        new_obj.controls.matrix *= self.controls.matrix

        from .base import Lifetime

        new_obj.lifetime = Lifetime(self.lifetime.start, self.lifetime.end)

        new_obj.scene = self.scene
        new_obj.ctx = self.scene.get_context()

        new_obj._dependencies.extend([new_obj.radius, new_obj.x, new_obj.y])
        return new_obj

geom property

geom

Return a reactive value of the transformed geometry.

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)
    self.apply_transform(move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing))
    return self

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_ ReactiveValue[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_: ReactiveValue[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.
    from_ = from_ or self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to.geom,
            from_,
            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 or 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,
        )
    )

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 or self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference,
            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,
        )
    )

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 from_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 from_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)

draw

draw()

Draw the shape with a simplified approach for all blend modes.

Source code in src/keyed/shapes.py
def draw(self) -> None:
    """Draw the shape with a simplified approach for all blend modes."""
    if self._direct_mode:
        self._draw_direct()
    else:
        self._draw()

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)

Circle

Bases: Shape

A circle.

Parameters:

Name Type Description Default
scene Scene

The scene to which the circle belongs.

required
x HasValue[float] | None

The x-coordinate of the center of the circle. Default is to center in the scene.

None
y HasValue[float] | None

The y-coordinate of the center of the circle. Default is to center in the scene.

None
radius HasValue[float]

The radius of the circle.

1
color tuple[float, float, float] | HasValue[Color]

The color of the circle's outline.

(1, 1, 1)
fill_color tuple[float, float, float] | HasValue[Color]

The fill color of the circle.

(1, 1, 1)
alpha float

The opacity level of the circle.

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

The dash pattern for the outline of the circle.

None
operator Operator

The compositing operator to use for drawing.

OPERATOR_OVER
draw_fill bool

Whether to fill the circle.

True
draw_stroke bool

Whether to draw the stroke of the circle.

True
line_width float

The width of the line used to stroke the circle.

2
Source code in src/keyed/shapes.py
class Circle(Shape):
    """A circle.

    Args:
        scene: The scene to which the circle belongs.
        x: The x-coordinate of the center of the circle. Default is to center in the scene.
        y: The y-coordinate of the center of the circle. Default is to center in the scene.
        radius: The radius of the circle.
        color: The color of the circle's outline.
        fill_color: The fill color of the circle.
        alpha: The opacity level of the circle.
        dash: The dash pattern for the outline of the circle.
        operator: The compositing operator to use for drawing.
        draw_fill: Whether to fill the circle.
        draw_stroke: Whether to draw the stroke of the circle.
        line_width: The width of the line used to stroke the circle.
    """

    def __init__(
        self,
        scene: Scene,
        x: HasValue[float] | None = None,
        y: HasValue[float] | None = None,
        radius: HasValue[float] = 1,
        color: tuple[float, float, float] | HasValue[Color] = (1, 1, 1),
        fill_color: tuple[float, float, float] | HasValue[Color] = (1, 1, 1),
        alpha: float = 1,
        dash: tuple[Sequence[float], float] | None = None,
        operator: cairo.Operator = cairo.OPERATOR_OVER,
        draw_fill: bool = True,
        draw_stroke: bool = True,
        line_width: float = 2,
    ) -> None:
        super().__init__(scene)
        self.scene = scene
        self.ctx = scene.get_context()
        self.x = x if x is not None else scene.nx(0.5)
        self.y = y if y is not None else scene.ny(0.5)
        self.controls.delta_x.value = self.x
        self.controls.delta_y.value = self.y
        self.radius = as_signal(radius)
        self.alpha = as_signal(alpha)
        self.color = as_color(color)
        self.fill_color = as_color(fill_color)
        self.dash = dash
        self.operator = operator
        self.draw_fill = draw_fill
        self.draw_stroke = draw_stroke
        self.line_width = as_signal(line_width)
        self.line_cap = cairo.LINE_CAP_BUTT
        self.line_join = cairo.LINE_JOIN_MITER
        self._dependencies.extend([self.radius, self.x, self.y])
        assert isinstance(self.controls.matrix, Signal)
        self.controls.matrix.value = self.controls.base_matrix()

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(x={self.x}, y={self.y}, radius={self.radius})"

    def _draw_shape(self) -> None:
        """Draw the circle."""
        r = self.radius.value
        self.ctx.move_to(r, 0)
        self.ctx.arc(0, 0, r, 0, 2 * math.pi)

    @property
    def _raw_geom_now(self) -> shapely.Polygon:
        """Return the geometry before any transformations.

        Returns:
            The polygon representing the circle.
        """
        return shapely.Point(0, 0).buffer(self.radius.value)

    def clone(self) -> Self:
        new_obj = self.__class__.__new__(self.__class__)
        new_obj.__dict__.update(self.__dict__)

        new_obj.controls = TransformControls(new_obj)
        new_obj.controls.matrix *= self.controls.matrix

        from .base import Lifetime

        new_obj.lifetime = Lifetime(self.lifetime.start, self.lifetime.end)

        new_obj.scene = self.scene
        new_obj.ctx = self.scene.get_context()

        new_obj._dependencies.extend([new_obj.radius, new_obj.x, new_obj.y])
        return new_obj

geom property

geom

Return a reactive value of the transformed geometry.

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)
    self.apply_transform(move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing))
    return self

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_ ReactiveValue[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_: ReactiveValue[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.
    from_ = from_ or self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to.geom,
            from_,
            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 or 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,
        )
    )

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 or self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference,
            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,
        )
    )

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 from_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 from_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)

draw

draw()

Draw the shape with a simplified approach for all blend modes.

Source code in src/keyed/shapes.py
def draw(self) -> None:
    """Draw the shape with a simplified approach for all blend modes."""
    if self._direct_mode:
        self._draw_direct()
    else:
        self._draw()

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)

Background

Bases: Rectangle

A rectangle that fills the scene.

Parameters:

Name Type Description Default
scene Scene

The scene to which the rectangle belongs.

required
color tuple[float, float, float] | HasValue[Color]

The color of the rectangle's border.

(1, 1, 1)
fill_color tuple[float, float, float] | HasValue[Color]

The fill color of the rectangle.

(1, 1, 1)
alpha HasValue[float]

The opacity level of the rectangle.

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

The dash pattern for the outline of the rectangle.

None
operator Operator

The compositing operator to use for drawing.

OPERATOR_OVER
draw_fill bool

Whether to fill the rectangle.

True
draw_stroke bool

Whether to draw the stroke of the rectangle.

True
line_width HasValue[float]

The width of the line used to stroke the rectangle.

2
Source code in src/keyed/shapes.py
class Background(Rectangle):
    """A rectangle that fills the scene.

    Args:
        scene: The scene to which the rectangle belongs.
        color: The color of the rectangle's border.
        fill_color: The fill color of the rectangle.
        alpha: The opacity level of the rectangle.
        dash: The dash pattern for the outline of the rectangle.
        operator: The compositing operator to use for drawing.
        draw_fill: Whether to fill the rectangle.
        draw_stroke: Whether to draw the stroke of the rectangle.
        line_width: The width of the line used to stroke the rectangle.
    """

    def __init__(
        self,
        scene: Scene,
        color: tuple[float, float, float] | HasValue[Color] = (1, 1, 1),
        fill_color: tuple[float, float, float] | HasValue[Color] = (1, 1, 1),
        alpha: HasValue[float] = 1,
        dash: tuple[Sequence[float], float] | None = None,
        operator: cairo.Operator = cairo.OPERATOR_OVER,
        draw_fill: bool = True,
        draw_stroke: bool = True,
        line_width: HasValue[float] = 2,
    ) -> None:
        super().__init__(
            scene=scene,
            color=color,
            width=scene.nx(1),
            height=scene.ny(1),
            fill_color=fill_color,
            alpha=alpha,
            dash=dash,
            operator=operator,
            draw_fill=draw_fill,
            draw_stroke=draw_stroke,
            line_width=line_width,
        )

geom property

geom

Return a reactive value of the transformed geometry.

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)
    self.apply_transform(move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing))
    return self

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_ ReactiveValue[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_: ReactiveValue[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.
    from_ = from_ or self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to.geom,
            from_,
            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 or 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,
        )
    )

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 or self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference,
            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,
        )
    )

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 from_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 from_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)

draw

draw()

Draw the shape with a simplified approach for all blend modes.

Source code in src/keyed/shapes.py
def draw(self) -> None:
    """Draw the shape with a simplified approach for all blend modes."""
    if self._direct_mode:
        self._draw_direct()
    else:
        self._draw()

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)

keyed.curve

Draw lines and curves.

Curve

Bases: Shape

Draw a curve through the a collection of object's centroids centroids.

Parameters:

Name Type Description Default
scene Scene

The scene to which the curve belongs.

required
objects Sequence[Base]

The objects through which the curve will pass.

required
color tuple[float, float, float] | Color

The color of the curve in RGB format.

(1, 1, 1)
fill_color tuple[float, float, float] | Color

The color of the curve's fill in RGB format.

(1, 1, 1)
alpha float

The transparency of the curve.

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

Dash pattern for the line, specified as a sequence of lengths and gaps.

None
operator Operator

The compositing operator to use for drawing.

OPERATOR_OVER
line_width float

The width of the curve line.

1
tension float

The tension factor used in calculating control points for the curve.

1

Raises:

Type Description
ValueError

If fewer than 2 objects are provided.

Source code in src/keyed/curve.py
class Curve(Shape):
    """Draw a curve through the a collection of object's centroids centroids.

    Args:
        scene: The scene to which the curve belongs.
        objects: The objects through which the curve will pass.
        color: The color of the curve in RGB format.
        fill_color: The color of the curve's fill in RGB format.
        alpha: The transparency of the curve.
        dash: Dash pattern for the line, specified as a sequence of lengths and gaps.
        operator: The compositing operator to use for drawing.
        line_width: The width of the curve line.
        tension: The tension factor used in calculating control points for the curve.

    Raises:
        ValueError: If fewer than 2 objects are provided.
    """

    def __init__(
        self,
        scene: Scene,
        objects: Sequence[Base],
        color: tuple[float, float, float] | Color = (1, 1, 1),
        fill_color: tuple[float, float, float] | Color = (1, 1, 1),
        alpha: float = 1,
        dash: tuple[Sequence[float], float] | None = None,
        operator: cairo.Operator = cairo.OPERATOR_OVER,
        line_width: float = 1,
        tension: float = 1,
        buffer: float = 30,
        draw_fill: bool = True,
        draw_stroke: bool = True,
    ):
        super().__init__(scene)
        if len(objects) < 2:
            raise ValueError("Need at least two objects")

        self.scene = scene
        self.ctx = scene.get_context()
        self.objects = objects
        self.color = as_color(color)
        self.fill_color = as_color(fill_color)
        self.alpha = Signal(alpha)
        self.dash = dash
        self.operator = operator
        self.draw_fill = draw_fill
        self.draw_stroke = draw_stroke
        self.line_width = Signal(line_width)
        self.tension = Signal(tension)
        self.buffer = Signal(buffer)
        self.line_cap = cairo.LINE_CAP_ROUND
        self.line_join = cairo.LINE_JOIN_ROUND
        self.start: ReactiveValue[float] = Signal(0.0)
        self.end: ReactiveValue[float] = Signal(1.0)

        # Add dependencies from child objects
        for item in self.objects:
            self._dependencies.extend(item.dependencies)

        # Initialize transform controls
        assert isinstance(self.controls.matrix, Signal)
        self.controls.matrix.value = self.controls.base_matrix()

    def _calculate_points(self) -> np.ndarray:
        """Calculate the points through which the curve will pass."""
        return np.array([obj.geom_now.centroid.coords[0] for obj in self.objects])

    def _get_partial_curve_points(self, points: np.ndarray) -> np.ndarray:
        """Get points for a partial curve based on start and end parameters."""
        if len(points) < 2:
            return points

        cp1, cp2 = _calculate_control_points(self.tension.value, points)

        # Calculate segment lengths for parameterization
        segment_lengths = np.array([bezier_length(*b) for b in zip(points[:-1], cp1, cp2, points[1:])])  # pyright: ignore[reportArgumentType]
        total_length = np.sum(segment_lengths)
        cumulative_lengths = np.hstack([0, np.cumsum(segment_lengths)])

        # Find segments containing start and end points
        start_length = self.start.value * total_length
        end_length = self.end.value * total_length

        start_idx = np.searchsorted(cumulative_lengths, start_length, "right") - 1
        end_idx = np.searchsorted(cumulative_lengths, end_length, "right") - 1

        # Calculate parametric positions within segments
        if start_idx < len(segment_lengths):
            start_t = (start_length - cumulative_lengths[start_idx]) / segment_lengths[start_idx]
        else:
            start_t = 1.0

        if end_idx < len(segment_lengths):
            end_t = (end_length - cumulative_lengths[end_idx]) / segment_lengths[end_idx]
        else:
            end_t = 1.0

        # Get partial curve points using de Casteljau's algorithm
        result_points = []

        # Add start point
        if start_idx < len(points) - 1:
            p0, p1, p2, p3 = points[start_idx], cp1[start_idx], cp2[start_idx], points[start_idx + 1]
            p0_new, _, _, _ = _de_casteljau(start_t, p0, p1, p2, p3, reverse=True)  # type: ignore
            result_points.append(p0_new)

        # Add intermediate points
        result_points.extend(points[start_idx + 1 : end_idx + 1])

        # Add end point
        if end_idx < len(points) - 1:
            p0, p1, p2, p3 = points[end_idx], cp1[end_idx], cp2[end_idx], points[end_idx + 1]
            _, _, _, p3_new = _de_casteljau(end_t, p0, p1, p2, p3)  # type: ignore
            result_points.append(p3_new)

        return np.array(result_points)

    def _create_curve_path(self, points: np.ndarray) -> None:
        """Create the curve path using bezier splines."""
        if len(points) < 2:
            return

        # Calculate control points
        cp1, cp2 = _calculate_control_points(self.tension.value, points)

        # Draw the curve
        self.ctx.move_to(*points[0])
        for i in range(len(points) - 1):
            self.ctx.curve_to(cp1[i][0], cp1[i][1], cp2[i][0], cp2[i][1], points[i + 1][0], points[i + 1][1])

    def _draw_shape(self) -> None:
        """Draw both the fill and stroke of the curve."""
        # Get full points and calculate partial curve points
        full_points = self._calculate_points()
        points = self._get_partial_curve_points(full_points)

        if len(points) < 2:
            return

        # Create the buffered geometry
        stroke_line = shapely.LineString(points)

        buffer_geom = stroke_line.buffer(self.buffer.value)
        coords = list(buffer_geom.exterior.coords)
        self.ctx.move_to(*coords[0])
        for coord in coords[1:]:
            self.ctx.line_to(*coord)
        self.ctx.close_path()

    @property
    def _raw_geom_now(self) -> shapely.Polygon:
        """Return the geometry before any transformations."""
        full_points = self._calculate_points()
        points = self._get_partial_curve_points(full_points)

        if len(points) < 2:
            return shapely.Polygon()

        # Create a line from the points and buffer it
        line = shapely.LineString(points)
        return line.buffer(self.buffer.value)

    @classmethod
    def from_points(
        cls,
        scene: Scene,
        points: Sequence[tuple[float, float]] | np.ndarray,
        color: tuple[float, float, float] | Color = (1, 1, 1),
        fill_color: tuple[float, float, float] | Color = (1, 1, 1),
        alpha: float = 1,
        dash: tuple[Sequence[float], float] | None = None,
        operator: cairo.Operator = cairo.OPERATOR_OVER,
        line_width: float = 1,
        tension: float = 1,
        buffer: float = 30,
        draw_fill: bool = True,
        draw_stroke: bool = True,
    ) -> Self:
        """Create a Curve object directly from a sequence of points."""
        objects = [Circle(scene, x, y, alpha=0) for (x, y) in points]
        return cls(
            scene=scene,
            objects=objects,
            color=color,
            fill_color=fill_color,
            alpha=alpha,
            dash=dash,
            operator=operator,
            line_width=line_width,
            tension=tension,
            buffer=buffer,
            draw_fill=draw_fill,
            draw_stroke=draw_stroke,
        )

    def write_on(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
        """Animate the line being drawn from start to end.

        Args:
            value: Value to animate to
            start: Frame to start the animation
            end: Frame to end the animation
            easing: Easing function to use

        Returns:
            Self
        """
        self.end = Animation(start, end, self.end, value, easing)(self.end, self.frame)
        return self

    def write_off(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
        """Animate the line being erased from end to start.

        Args:
            value: Value to animate to
            start: Frame to start the animation
            end: Frame to end the animation
            easing: Easing function to use

        Returns:
            Self
        """
        self.start = Animation(start, end, self.start, value, easing)(self.start, self.frame)
        return self

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(...)"

geom property

geom

Return a reactive value of the transformed geometry.

from_points classmethod

from_points(scene, points, color=(1, 1, 1), fill_color=(1, 1, 1), alpha=1, dash=None, operator=OPERATOR_OVER, line_width=1, tension=1, buffer=30, draw_fill=True, draw_stroke=True)

Create a Curve object directly from a sequence of points.

Source code in src/keyed/curve.py
@classmethod
def from_points(
    cls,
    scene: Scene,
    points: Sequence[tuple[float, float]] | np.ndarray,
    color: tuple[float, float, float] | Color = (1, 1, 1),
    fill_color: tuple[float, float, float] | Color = (1, 1, 1),
    alpha: float = 1,
    dash: tuple[Sequence[float], float] | None = None,
    operator: cairo.Operator = cairo.OPERATOR_OVER,
    line_width: float = 1,
    tension: float = 1,
    buffer: float = 30,
    draw_fill: bool = True,
    draw_stroke: bool = True,
) -> Self:
    """Create a Curve object directly from a sequence of points."""
    objects = [Circle(scene, x, y, alpha=0) for (x, y) in points]
    return cls(
        scene=scene,
        objects=objects,
        color=color,
        fill_color=fill_color,
        alpha=alpha,
        dash=dash,
        operator=operator,
        line_width=line_width,
        tension=tension,
        buffer=buffer,
        draw_fill=draw_fill,
        draw_stroke=draw_stroke,
    )

write_on

write_on(value, start, end, easing=cubic_in_out)

Animate the line being drawn from start to end.

Parameters:

Name Type Description Default
value HasValue[float]

Value to animate to

required
start int

Frame to start the animation

required
end int

Frame to end the animation

required
easing EasingFunctionT

Easing function to use

cubic_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/curve.py
def write_on(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
    """Animate the line being drawn from start to end.

    Args:
        value: Value to animate to
        start: Frame to start the animation
        end: Frame to end the animation
        easing: Easing function to use

    Returns:
        Self
    """
    self.end = Animation(start, end, self.end, value, easing)(self.end, self.frame)
    return self

write_off

write_off(value, start, end, easing=cubic_in_out)

Animate the line being erased from end to start.

Parameters:

Name Type Description Default
value HasValue[float]

Value to animate to

required
start int

Frame to start the animation

required
end int

Frame to end the animation

required
easing EasingFunctionT

Easing function to use

cubic_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/curve.py
def write_off(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
    """Animate the line being erased from end to start.

    Args:
        value: Value to animate to
        start: Frame to start the animation
        end: Frame to end the animation
        easing: Easing function to use

    Returns:
        Self
    """
    self.start = Animation(start, end, self.start, value, easing)(self.start, self.frame)
    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)
    self.apply_transform(move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing))
    return self

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_ ReactiveValue[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_: ReactiveValue[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.
    from_ = from_ or self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to.geom,
            from_,
            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 or 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,
        )
    )

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 or self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference,
            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,
        )
    )

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 from_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 from_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)

draw

draw()

Draw the shape with a simplified approach for all blend modes.

Source code in src/keyed/shapes.py
def draw(self) -> None:
    """Draw the shape with a simplified approach for all blend modes."""
    if self._direct_mode:
        self._draw_direct()
    else:
        self._draw()

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)

bezier_length

bezier_length(p0, p1, p2, p3)

Calculate the length of a cubic Bézier curve using numerical integration.

Parameters:

Name Type Description Default
p0 Vector

First control point of the cubic Bézier curve.

required
p1 Vector

Second

required
p2 Vector

Third

required
p3 Vector

Fourth

required

Returns:

Type Description
Vector

The total length of the cubic Bézier curve.

Source code in src/keyed/curve.py
def bezier_length(p0: Vector, p1: Vector, p2: Vector, p3: Vector) -> Vector:
    """Calculate the length of a cubic Bézier curve using numerical integration.

    Args:
        p0: First control point of the cubic Bézier curve.
        p1: Second
        p2: Third
        p3: Fourth

    Returns:
        The total length of the cubic Bézier curve.
    """
    assert p0.shape == (2,)
    assert p1.shape == (2,)
    assert p2.shape == (2,)
    assert p3.shape == (2,)
    f_integral = partial(_integrand, p0=p0, p1=p1, p2=p2, p3=p3)
    arclength, _ = quad(f_integral, 0, 1)
    return arclength

keyed.line

Line

Bases: Base

Draw a line between two points.

Parameters:

Name Type Description Default
x0 HasValue[float] | None

x coordinate of first point.

None
y0 HasValue[float] | None

y coordinate of first point.

None
x1 HasValue[float] | None

x coordinate of second point.

None
y1 HasValue[float] | None

y coordinate of second point.

None
color HasValue[Color] | tuple[float, float, float]

Line color.

(1, 1, 1)
alpha HasValue[float]

Transparency.

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

Dash specification.

None
operator Operator

Blend mode.

OPERATOR_OVER
line_width HasValue[float]

width of line.

1
Source code in src/keyed/line.py
class Line(Base):
    """Draw a line between two points.

    Args:
        x0: x coordinate of first point.
        y0: y coordinate of first point.
        x1: x coordinate of second point.
        y1: y coordinate of second point.
        color: Line color.
        alpha: Transparency.
        dash: Dash specification.
        operator: Blend mode.
        line_width: width of line.
    """

    def __init__(
        self,
        scene: Scene,
        x0: HasValue[float] | None = None,
        y0: HasValue[float] | None = None,
        x1: HasValue[float] | None = None,
        y1: HasValue[float] | None = None,
        color: HasValue[Color] | tuple[float, float, float] = (1, 1, 1),
        alpha: HasValue[float] = 1,
        dash: tuple[Sequence[float], float] | None = None,
        operator: cairo.Operator = cairo.OPERATOR_OVER,
        line_width: HasValue[float] = 1,
    ) -> None:
        super().__init__(scene)
        self.scene = scene
        self.ctx = scene.get_context()
        self.start: ReactiveValue[float] = Signal(0)
        self.end: ReactiveValue[float] = Signal(1)
        self.x0 = as_signal(x0 if x0 is not None else 0)
        self.y0 = as_signal(y0 if y0 is not None else scene.ny(0.5))
        self.x1 = as_signal(x1 if x1 is not None else scene.nx(1))
        self.y1 = as_signal(y1 if y1 is not None else scene.ny(0.5))
        self.color = as_color(color)
        self.alpha = as_signal(alpha)
        self.dash = dash
        self.operator = operator
        self.line_cap = cairo.LINE_CAP_ROUND
        self.line_join = cairo.LINE_JOIN_ROUND
        # consider adding line cap/join to args
        self.line_width = as_signal(line_width)
        self.draw_fill = False
        self.draw_stroke = True
        # Todo consider how to draw outlined line.
        self._dependencies.extend([self.x0, self.x1, self.y0, self.y1])
        assert isinstance(self.controls.matrix, Signal)
        self.controls.matrix.value = self.controls.base_matrix()

    @contextmanager
    def _style(self) -> Generator[None, None, None]:
        """Context manager for setting up the drawing style for the shape.

        Temporarily sets various drawing properties such as line width, line cap, line join,
        dash pattern, and operator based on the shape's attributes.

        Yields:
            None: Yields control back to the caller within the context of the configured style.
        """
        try:
            self.ctx.save()
            if self.dash is not None:
                self.ctx.set_dash(*self.dash)
            self.ctx.set_operator(self.operator)
            self.ctx.set_line_width(self.line_width.value)
            self.ctx.set_line_cap(self.line_cap)
            self.ctx.set_line_join(self.line_join)
            yield
        finally:
            self.ctx.restore()

    def draw(self) -> None:
        """Draw the shape within its styled context, applying transformations."""
        x0 = lerp(self.x0.value, self.x1.value, self.start.value)
        y0 = lerp(self.y0.value, self.y1.value, self.start.value)
        x1 = lerp(self.x0.value, self.x1.value, self.end.value)
        y1 = lerp(self.y0.value, self.y1.value, self.end.value)

        if x0 == x1 and y0 == y1:
            return

        with self._style():
            self.ctx.set_matrix(self.controls.matrix.value)
            self.ctx.move_to(x0, y0)
            self.ctx.line_to(x1, y1)
            self.ctx.set_source_rgba(*unref(self.color).rgb, self.alpha.value)
            self.ctx.stroke()
            self.ctx.set_matrix(cairo.Matrix())

    def cleanup(self) -> None:
        if isinstance(self.ctx, Cleanable):
            self.ctx.cleanup()

    @property
    def _raw_geom_now(self) -> shapely.LineString:
        x0 = lerp(self.x0.value, self.x1.value, self.start.value)
        y0 = lerp(self.y0.value, self.y1.value, self.start.value)
        x1 = lerp(self.x0.value, self.x1.value, self.end.value)
        y1 = lerp(self.y0.value, self.y1.value, self.end.value)
        return shapely.LineString([[x0, y0], [x1, y1]])

    def write_on(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
        """Animate the line being drawn from start to end.

        Args:
            value: Value to animate to
            start: Frame to start the animation
            end: Frame to end the animation
            easing: Easing function to use

        Returns:
            Self
        """
        self.end = Animation(start, end, self.end, value, easing)(self.end, self.frame)
        return self

    def write_off(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
        """Animate the line being erased from end to start.

        Args:
            value: Value to animate to
            start: Frame to start the animation
            end: Frame to end the animation
            easing: Easing function to use

        Returns:
            Self
        """
        self.start = Animation(start, end, self.start, value, easing)(self.start, self.frame)
        return self

    def __repr__(self) -> str:
        return f"Line(x0={self.x0}, y0={self.y0}, x1={self.x1}, y1={self.y1})"

geom property

geom

Return a reactive value of the transformed geometry.

draw

draw()

Draw the shape within its styled context, applying transformations.

Source code in src/keyed/line.py
def draw(self) -> None:
    """Draw the shape within its styled context, applying transformations."""
    x0 = lerp(self.x0.value, self.x1.value, self.start.value)
    y0 = lerp(self.y0.value, self.y1.value, self.start.value)
    x1 = lerp(self.x0.value, self.x1.value, self.end.value)
    y1 = lerp(self.y0.value, self.y1.value, self.end.value)

    if x0 == x1 and y0 == y1:
        return

    with self._style():
        self.ctx.set_matrix(self.controls.matrix.value)
        self.ctx.move_to(x0, y0)
        self.ctx.line_to(x1, y1)
        self.ctx.set_source_rgba(*unref(self.color).rgb, self.alpha.value)
        self.ctx.stroke()
        self.ctx.set_matrix(cairo.Matrix())

write_on

write_on(value, start, end, easing=cubic_in_out)

Animate the line being drawn from start to end.

Parameters:

Name Type Description Default
value HasValue[float]

Value to animate to

required
start int

Frame to start the animation

required
end int

Frame to end the animation

required
easing EasingFunctionT

Easing function to use

cubic_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/line.py
def write_on(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
    """Animate the line being drawn from start to end.

    Args:
        value: Value to animate to
        start: Frame to start the animation
        end: Frame to end the animation
        easing: Easing function to use

    Returns:
        Self
    """
    self.end = Animation(start, end, self.end, value, easing)(self.end, self.frame)
    return self

write_off

write_off(value, start, end, easing=cubic_in_out)

Animate the line being erased from end to start.

Parameters:

Name Type Description Default
value HasValue[float]

Value to animate to

required
start int

Frame to start the animation

required
end int

Frame to end the animation

required
easing EasingFunctionT

Easing function to use

cubic_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/line.py
def write_off(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
    """Animate the line being erased from end to start.

    Args:
        value: Value to animate to
        start: Frame to start the animation
        end: Frame to end the animation
        easing: Easing function to use

    Returns:
        Self
    """
    self.start = Animation(start, end, self.start, value, easing)(self.start, self.frame)
    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)
    self.apply_transform(move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing))
    return self

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_ ReactiveValue[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_: ReactiveValue[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.
    from_ = from_ or self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to.geom,
            from_,
            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 or 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,
        )
    )

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 or self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference,
            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,
        )
    )

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 from_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 from_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)

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)

BezierCurve

Bases: Base

Source code in src/keyed/line.py
class BezierCurve(Base):
    def __init__(
        self,
        scene: Scene,
        x0: HasValue[float],
        y0: HasValue[float],
        x1: HasValue[float],
        y1: HasValue[float],
        x2: HasValue[float],
        y2: HasValue[float],
        x3: HasValue[float],
        y3: HasValue[float],
        color: HasValue[Color] | tuple[float, float, float] = (1, 1, 1),
        alpha: HasValue[float] = 1,
        dash: tuple[Sequence[float], float] | None = None,
        operator: cairo.Operator = cairo.OPERATOR_OVER,
        line_width: HasValue[float] = 1,
    ) -> None:
        super().__init__(scene)
        self.scene = scene
        self.ctx = scene.get_context()
        self.start: ReactiveValue[float] = Signal(0.0)
        self.end: ReactiveValue[float] = Signal(1.0)
        self.x0 = as_signal(x0)
        self.y0 = as_signal(y0)
        self.x1 = as_signal(x1)
        self.y1 = as_signal(y1)
        self.x2 = as_signal(x2)
        self.y2 = as_signal(y2)
        self.x3 = as_signal(x3)
        self.y3 = as_signal(y3)
        self.color = as_color(color)
        self.alpha = as_signal(alpha)
        self.dash = dash
        self.operator = operator
        self.line_cap = cairo.LINE_CAP_ROUND
        self.line_join = cairo.LINE_JOIN_ROUND
        # consider adding line cap/join to args
        self.line_width = as_signal(line_width)
        self.draw_fill = False
        self.draw_stroke = True
        # Todo consider how to draw outlined line.
        self._dependencies.extend(
            [
                self.x0,
                self.y0,
                self.x1,
                self.x2,
                self.y1,
                self.y2,
                self.x3,
                self.y3,
            ]
        )
        assert isinstance(self.controls.matrix, Signal)
        self.controls.matrix.value = self.controls.base_matrix()

    @contextmanager
    def _style(self) -> Generator[None, None, None]:
        """Context manager for setting up the drawing style for the shape.

        Temporarily sets various drawing properties such as line width, line cap, line join,
        dash pattern, and operator based on the shape's attributes.

        Yields:
            None: Yields control back to the caller within the context of the configured style.
        """
        try:
            self.ctx.save()
            if self.dash is not None:
                self.ctx.set_dash(*self.dash)
            self.ctx.set_operator(self.operator)
            self.ctx.set_line_width(self.line_width.value)
            self.ctx.set_line_cap(self.line_cap)
            self.ctx.set_line_join(self.line_join)
            yield
        finally:
            self.ctx.restore()

    def cleanup(self) -> None:
        if isinstance(self.ctx, Cleanable):
            self.ctx.cleanup()

    def control_points(
        self,
    ) -> tuple[
        ReactiveValue[float],
        ReactiveValue[float],
        ReactiveValue[float],
        ReactiveValue[float],
        ReactiveValue[float],
        ReactiveValue[float],
        ReactiveValue[float],
        ReactiveValue[float],
    ]:
        # Update the control points, based on self.start
        x0, x1, x2, x3 = _de_casteljau(self.start, self.x0, self.x1, self.x2, self.x3, reverse=True)
        y0, y1, y2, y3 = _de_casteljau(self.start, self.y0, self.y1, self.y2, self.y3, reverse=True)

        # Update the control points, based on self.end
        x0, x1, x2, x3 = _de_casteljau(self.end, x0, x1, x2, x3, reverse=False)
        y0, y1, y2, y3 = _de_casteljau(self.end, y0, y1, y2, y3, reverse=False)
        return x0, y0, x1, y1, x2, y2, x3, y3

    def draw(self) -> None:
        """Draw the shape within its styled context, applying transformations."""
        x0, y0, x1, y1, x2, y2, x3, y3 = self.control_points()
        if x0 == x3 and y0 == y3:
            return

        with self._style():
            self.ctx.set_matrix(self.controls.matrix.value)

            # Draw the curve using the calculated control points
            self.ctx.move_to(x0.value, y0.value)
            self.ctx.curve_to(x1.value, y1.value, x2.value, y2.value, x3.value, y3.value)
            self.ctx.set_source_rgba(*unref(self.color).rgb, self.alpha.value)
            self.ctx.stroke()
            self.ctx.set_matrix(cairo.Matrix())

    @property
    def _raw_geom_now(self) -> shapely.LineString:
        """Raw geometry of the Bezier curve using De Casteljau for the start and end."""
        x0, y0, x1, y1, x2, y2, x3, y3 = self.control_points()

        return shapely.LineString(
            [[x0.value, y0.value], [x1.value, y1.value], [x2.value, y2.value], [x3.value, y3.value]]
        )

    def write_on(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
        """Animate the line being drawn from start to end.

        Args:
            value: Value to animate to
            start: Frame to start the animation
            end: Frame to end the animation
            easing: Easing function to use

        Returns:
            Self
        """
        self.end = Animation(start, end, self.end, value, easing)(self.end, self.frame)
        return self

    def write_off(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
        """Animate the line being erased from end to start.

        Args:
            value: Value to animate to
            start: Frame to start the animation
            end: Frame to end the animation
            easing: Easing function to use

        Returns:
            Self
        """
        self.start = Animation(start, end, self.start, value, easing)(self.start, self.frame)
        return self

    def __repr__(self) -> str:
        return (
            f"BezierCurve(x0={self.x0.value}, y0={self.y0.value}, "
            f"x1={self.x1.value}, y1={self.y1.value}, x2={self.x2.value}, y2={self.y2.value}, "
            f"x3={self.x3.value}, y3={self.y3.value})"
        )

geom property

geom

Return a reactive value of the transformed geometry.

draw

draw()

Draw the shape within its styled context, applying transformations.

Source code in src/keyed/line.py
def draw(self) -> None:
    """Draw the shape within its styled context, applying transformations."""
    x0, y0, x1, y1, x2, y2, x3, y3 = self.control_points()
    if x0 == x3 and y0 == y3:
        return

    with self._style():
        self.ctx.set_matrix(self.controls.matrix.value)

        # Draw the curve using the calculated control points
        self.ctx.move_to(x0.value, y0.value)
        self.ctx.curve_to(x1.value, y1.value, x2.value, y2.value, x3.value, y3.value)
        self.ctx.set_source_rgba(*unref(self.color).rgb, self.alpha.value)
        self.ctx.stroke()
        self.ctx.set_matrix(cairo.Matrix())

write_on

write_on(value, start, end, easing=cubic_in_out)

Animate the line being drawn from start to end.

Parameters:

Name Type Description Default
value HasValue[float]

Value to animate to

required
start int

Frame to start the animation

required
end int

Frame to end the animation

required
easing EasingFunctionT

Easing function to use

cubic_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/line.py
def write_on(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
    """Animate the line being drawn from start to end.

    Args:
        value: Value to animate to
        start: Frame to start the animation
        end: Frame to end the animation
        easing: Easing function to use

    Returns:
        Self
    """
    self.end = Animation(start, end, self.end, value, easing)(self.end, self.frame)
    return self

write_off

write_off(value, start, end, easing=cubic_in_out)

Animate the line being erased from end to start.

Parameters:

Name Type Description Default
value HasValue[float]

Value to animate to

required
start int

Frame to start the animation

required
end int

Frame to end the animation

required
easing EasingFunctionT

Easing function to use

cubic_in_out

Returns:

Type Description
Self

Self

Source code in src/keyed/line.py
def write_off(self, value: HasValue[float], start: int, end: int, easing: EasingFunctionT = cubic_in_out) -> Self:
    """Animate the line being erased from end to start.

    Args:
        value: Value to animate to
        start: Frame to start the animation
        end: Frame to end the animation
        easing: Easing function to use

    Returns:
        Self
    """
    self.start = Animation(start, end, self.start, value, easing)(self.start, self.frame)
    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)
    self.apply_transform(move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing))
    return self

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_ ReactiveValue[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_: ReactiveValue[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.
    from_ = from_ or self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to.geom,
            from_,
            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 or 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,
        )
    )

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 or self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference,
            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,
        )
    )

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 from_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 from_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)

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)

bezier_point

bezier_point(t, x0, y0, x1, y1, x2, y2, x3, y3)

Calculate a point on a cubic Bezier curve at parameter t.

Parameters:

Name Type Description Default
t float

The parameter between 0 and 1.

required
x0 float

The x coordinate of the first control point of the cubic Bézier curve.

required
y0 float

The y coordinate of the first control point of the cubic Bézier curve.

required
x1 float

Second x

required
y1 float

Second y

required
x2 float

Third x

required
y2 float

Third y

required
x3 float

Fourth x

required
y3 float

Fourth y

required

Returns: tuple: The point on the curve at parameter t.

Source code in src/keyed/line.py
def bezier_point(
    t: float, x0: float, y0: float, x1: float, y1: float, x2: float, y2: float, x3: float, y3: float
) -> tuple[float, float]:
    """
    Calculate a point on a cubic Bezier curve at parameter t.

    Args:
        t (float): The parameter between 0 and 1.
        x0: The x coordinate of the first control point of the cubic Bézier curve.
        y0: The y coordinate of the first control point of the cubic Bézier curve.
        x1: Second x
        y1: Second y
        x2: Third x
        y2: Third y
        x3: Fourth x
        y3: Fourth y
    Returns:
        tuple: The point on the curve at parameter t.
    """
    # Cubic Bezier formula
    x = (1 - t) ** 3 * x0 + 3 * (1 - t) ** 2 * t * x1 + 3 * (1 - t) * t**2 * x2 + t**3 * x3
    y = (1 - t) ** 3 * y0 + 3 * (1 - t) ** 2 * t * y1 + 3 * (1 - t) * t**2 * y2 + t**3 * y3
    return (x, y)

approximate_bezier_as_linestring

approximate_bezier_as_linestring(x0, y0, x1, y1, x2, y2, x3, y3, num_points=20)

Approximate a cubic Bezier curve as a Shapely LineString.

Parameters:

Name Type Description Default
x0 float

The x coordinate of the first control point of the cubic Bézier curve.

required
y0 float

The y coordinate of the first control point of the cubic Bézier curve.

required
x1 float

Second x

required
y1 float

Second y

required
x2 float

Third x

required
y2 float

Third y

required
x3 float

Fourth x

required
y3 float

Fourth y

required
num_points int

Number of points to sample along the curve.

20

Returns:

Type Description
LineString

shapely.geometry.LineString: The approximated curve as a LineString.

Source code in src/keyed/line.py
def approximate_bezier_as_linestring(
    x0: float,
    y0: float,
    x1: float,
    y1: float,
    x2: float,
    y2: float,
    x3: float,
    y3: float,
    num_points: int = 20,
) -> shapely.LineString:
    """
    Approximate a cubic Bezier curve as a Shapely LineString.

    Args:
        x0: The x coordinate of the first control point of the cubic Bézier curve.
        y0: The y coordinate of the first control point of the cubic Bézier curve.
        x1: Second x
        y1: Second y
        x2: Third x
        y2: Third y
        x3: Fourth x
        y3: Fourth y
        num_points (int): Number of points to sample along the curve.

    Returns:
        shapely.geometry.LineString: The approximated curve as a LineString.
    """
    points = [bezier_point(t, x0, y0, x1, y1, x2, y2, x3, y3) for t in np.linspace(0, 1, num_points)]
    return shapely.LineString(points)

keyed.geometry

Object that draws directly from a shapely geometry.

Geometry

Bases: Shape

A shape defined by an arbitrary shapely geometry.

This class supports shapely geometries of type Polygon, MultiPolygon, LineString, Point, etc.

Parameters:

Name Type Description Default
scene Scene

The scene to which the shape belongs.

required
geometry HasValue[BaseGeometry]

The shapely geometry that defines the shape.

required
color Union[tuple[float, float, float], Color]

The RGB color of the shape's outline.

(1, 1, 1)
fill_color Union[tuple[float, float, float], Color]

The RGB color of the shape's fill.

(1, 1, 1)
alpha float

The opacity of the shape.

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

The dash pattern for the shape's outline.

None
operator Operator

The compositing operator to use for drawing.

OPERATOR_OVER
line_cap LineCap

The line cap style to use.

LINE_CAP_ROUND
line_join LineJoin

The line join style to use.

LINE_JOIN_ROUND
line_width float

The width of the shape's outline.

1
buffer float

The buffer distance to apply around the shape.

0
draw_fill bool

Whether to fill the shape.

True
draw_stroke bool

Whether to draw the outline.

True
Source code in src/keyed/geometry.py
class Geometry(Shape):
    """A shape defined by an arbitrary shapely geometry.

    This class supports shapely geometries of type Polygon, MultiPolygon, LineString, Point, etc.

    Args:
        scene: The scene to which the shape belongs.
        geometry: The shapely geometry that defines the shape.
        color: The RGB color of the shape's outline.
        fill_color: The RGB color of the shape's fill.
        alpha: The opacity of the shape.
        dash: The dash pattern for the shape's outline.
        operator: The compositing operator to use for drawing.
        line_cap: The line cap style to use.
        line_join: The line join style to use.
        line_width: The width of the shape's outline.
        buffer: The buffer distance to apply around the shape.
        draw_fill: Whether to fill the shape.
        draw_stroke: Whether to draw the outline.
    """

    def __init__(
        self,
        scene: Scene,
        geometry: HasValue[BaseGeometry],
        color: Union[tuple[float, float, float], Color] = (1, 1, 1),
        fill_color: Union[tuple[float, float, float], Color] = (1, 1, 1),
        alpha: float = 1,
        dash: Union[tuple[Sequence[float], float], None] = None,
        operator: cairo.Operator = cairo.OPERATOR_OVER,
        line_cap: cairo.LineCap = cairo.LINE_CAP_ROUND,
        line_join: cairo.LineJoin = cairo.LINE_JOIN_ROUND,
        line_width: float = 1,
        buffer: float = 0,
        draw_fill: bool = True,
        draw_stroke: bool = True,
        center_geometry: bool = True,
    ):
        super().__init__(scene)
        self.scene = scene
        self.ctx = scene.get_context()
        g = as_signal(geometry)
        self.geometry = center(g, scene) if center_geometry else g
        self.color = as_color(color)
        self.fill_color = as_color(fill_color)
        self.alpha = Signal(alpha)
        self.dash = dash
        self.operator = operator
        self.draw_fill = draw_fill
        self.draw_stroke = draw_stroke
        self.line_width = Signal(line_width)
        self.buffer = Signal(buffer)
        self.line_cap = line_cap
        self.line_join = line_join

        self._dependencies.extend([self.geometry, self.buffer])
        assert isinstance(self.controls.matrix, Signal)
        self.controls.matrix.value = self.controls.base_matrix()

    def _draw_point(self, point: shapely.Point) -> None:
        """Draw a Point geometry.

        Args:
            point: The Point geometry to draw.
        """
        self.ctx.new_path()
        x, y = point.coords[0]
        self.ctx.arc(x, y, self.line_width.value / 2, 0, 2 * math.pi)

        if self.draw_fill:
            self._apply_fill(self.ctx)
            if self.draw_stroke:
                self.ctx.fill_preserve()
            else:
                self.ctx.fill()
        if self.draw_stroke:
            self._apply_stroke(self.ctx)
            self.ctx.stroke()

    def _draw_linestring(self, linestring: shapely.LineString) -> None:
        """Draw a LineString geometry.

        Args:
            linestring: The LineString geometry to draw.
        """
        self.ctx.new_path()
        coords = list(linestring.coords)
        if not coords:
            return

        self.ctx.move_to(*coords[0])
        for coord in coords[1:]:
            self.ctx.line_to(*coord)

        if self.draw_fill:
            self._apply_fill(self.ctx)
            if self.draw_stroke:
                self.ctx.fill_preserve()
            else:
                self.ctx.fill()
        if self.draw_stroke:
            self._apply_stroke(self.ctx)
            self.ctx.stroke()

    def _draw_polygon(self, polygon: shapely.Polygon) -> None:
        """Draw a Polygon geometry.

        Args:
            polygon: The Polygon geometry to draw.
        """
        self.ctx.new_path()
        # Set fill rule to create proper holes
        # self.ctx.set_fill_rule(cairo.FILL_RULE_WINDING)

        # Draw exterior ring clockwise
        coords = list(polygon.exterior.coords)
        if not coords:
            return

        self.ctx.move_to(*coords[0])
        for coord in coords[1:]:
            self.ctx.line_to(*coord)
        self.ctx.close_path()

        # Draw interior rings (holes) counter-clockwise
        for interior in polygon.interiors:
            coords = list(interior.coords)
            self.ctx.move_to(*coords[0])
            # Reverse the coordinates to go in opposite direction
            for coord in reversed(coords[1:]):
                self.ctx.line_to(*coord)
            self.ctx.close_path()

        # self.ctx.clip_preserve()

        if self.draw_fill:
            self._apply_fill(self.ctx)
            if self.draw_stroke:
                self.ctx.fill_preserve()
            else:
                self.ctx.fill()
        if self.draw_stroke:
            self._apply_stroke(self.ctx)
            self.ctx.stroke()

    def _draw_geometry(self, geom: BaseGeometry) -> None:
        """Draw any shapely geometry by dispatching to the appropriate drawing method.

        Args:
            geom: The geometry to draw.
        """
        if isinstance(geom, shapely.Point):
            self._draw_point(geom)
        elif isinstance(geom, shapely.LineString):
            self._draw_linestring(geom)
        elif isinstance(geom, shapely.Polygon):
            self._draw_polygon(geom)
        elif isinstance(geom, (shapely.MultiPoint, shapely.MultiLineString, shapely.MultiPolygon)):
            for part in geom.geoms:
                self._draw_geometry(part)
        elif isinstance(geom, shapely.GeometryCollection):
            for part in geom.geoms:
                self._draw_geometry(part)

    def _draw_shape(self) -> None:
        """Draw the shape by handling any type of shapely geometry."""
        geometry = unref(self.geometry)

        if geometry.is_empty:
            return

        # Apply buffer if specified
        if self.buffer.value > 0:
            geometry = geometry.buffer(self.buffer.value)

        self._draw_geometry(geometry)

    @property
    def _raw_geom_now(self) -> BaseGeometry:
        """Return the geometry before any transformations.

        Returns:
            The raw geometry.
        """
        geometry = unref(self.geometry)
        if self.buffer.value > 0:
            return geometry.buffer(self.buffer.value)
        return geometry

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}(geometry={self.geometry}, buffer={self.buffer}, line_width={self.line_width})"
        )

geom property

geom

Return a reactive value of the transformed geometry.

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)
    self.apply_transform(move_to(start=start, end=end, x=x, y=y, cx=cx, cy=cy, frame=self.frame, easing=easing))
    return self

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_ ReactiveValue[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_: ReactiveValue[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.
    from_ = from_ or self.geom
    lock = lock if lock is not None else end
    return self.apply_transform(
        align_to(
            to.geom,
            from_,
            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 or 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,
        )
    )

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 or self.geom
    return self.apply_transform(
        align_now(
            target=target.geom,
            reference=reference,
            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,
        )
    )

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 from_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 from_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)

draw

draw()

Draw the shape with a simplified approach for all blend modes.

Source code in src/keyed/shapes.py
def draw(self) -> None:
    """Draw the shape with a simplified approach for all blend modes."""
    if self._direct_mode:
        self._draw_direct()
    else:
        self._draw()

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)

center

center(geometry, scene)

Returns a geometry translated such that its centroid is at (0,0).

Source code in src/keyed/geometry.py
@computed
def center(geometry, scene):
    """Returns a geometry translated such that its centroid is at (0,0)."""
    centroid = geometry.centroid
    return translate(geometry, xoff=-centroid.x + scene.nx(0.5), yoff=-centroid.y + scene.ny(0.5))