← Files AgentBrokoARCHIVED FILE

src/video_forge/scenes/message.py

2.71 KB · Sep 30, 2026 · 23:15 UTC

↓ Download file

"""message — a chat bubble with a secondary "engine" card and a round call button.
Reference: the "I need to reschedule my delivery" beat.

scene fields: message (bubble text), label (secondary card label, e.g. product
name), toggle (bool), call (bool)."""

from ..easing import ease_out, lerp, ramp
from ._common import enter, new_canvas, wrap_lines

PHONE = chr(0xE717)   # Segoe MDL2 Assets — Phone
SEND = chr(0xE724)    # Segoe MDL2 Assets — Send


def render(ctx):
    cv = new_canvas(ctx)
    W, H = cv.W, cv.H
    p = ctx.p
    msg = ctx.scene.get("message", "")
    label = ctx.scene.get("label", ctx.theme.brand.get("name", ""))
    show_toggle = ctx.scene.get("toggle", True)
    show_call = ctx.scene.get("call", True)

    bubble_w = 720.0
    pad = 44.0
    size = 40
    lines = wrap_lines(cv, msg, "regular", size, bubble_w - pad * 2)
    lh = size * 1.32
    bubble_h = max(150.0, len(lines) * lh + pad * 2)
    cx = W / 2 - (60 if show_call else 0)
    by = H / 2 - bubble_h / 2 - 40

    # secondary "engine" card, slightly behind & below
    kc = enter(p, 0.18, 0.45)
    if label:
        sw, sh = bubble_w - 60, 96
        sx = cx - sw / 2
        sy = by + bubble_h - 28 + (1 - kc) * 20
        cv.card(sx, sy, sw, sh, r=22, fill=cv.pal["card2"], alpha=kc, elevate=18)
        cv.text(sx + 40, sy + sh / 2, label, "medium", 32, (24, 24, 24),
                anchor="lm", alpha=kc)
        if show_toggle:
            tw, th = 92, 46
            tx = sx + sw - 40 - tw
            ty = sy + sh / 2 - th / 2
            cv.pill(tx, ty, tw, th, fill=cv.accent, alpha=kc)
            kr = th - 10
            cv.ellipse(tx + tw - th / 2, ty + th / 2, kr / 2, fill=(255, 255, 255),
                       alpha=kc)

    # main bubble
    kb = enter(p, 0.05, 0.4)
    sc = lerp(0.97, 1.0, kb)
    bw2, bh2 = bubble_w * sc, bubble_h * sc
    bx2, by2 = cx - bw2 / 2, by - (bh2 - bubble_h) / 2
    cv.card(bx2, by2, bw2, bh2, r=30, fill=(255, 255, 255), alpha=kb, elevate=30)
    ty = by2 + pad + lh * 0.5
    for i, ln in enumerate(lines):
        cv.text(bx2 + pad, ty + i * lh, ln, "regular", size, (70, 70, 70),
                anchor="lm", alpha=kb)
    # small send glyph, bottom-right
    cv.text(bx2 + bw2 - 42, by2 + bh2 - 36, SEND, "icon", 24, (188, 188, 188),
            anchor="mm", alpha=kb)

    # round call button
    if show_call:
        ka = ease_out(ramp(p, 0.3, 0.7))
        r = 52
        bxc = cx + bubble_w / 2 + 20 + r
        byc = by + bubble_h / 2
        cv.drop_shadow(bxc - r, byc - r, 2 * r, 2 * r, r, spread=18,
                       alpha=0.18 * ka, dy=8)
        cv.ellipse(bxc, byc, r, fill=(255, 255, 255), alpha=ka)
        cv.text_center(bxc, byc, PHONE, "icon", 40, (20, 20, 20), alpha=ka)
    return cv.finish()

SHA-256: 05f66aa9a7cef3369a4b17414d93e702ecf765c6c588ea93c6b1374dcabe37a1