Source code for bascenev1lib.actor.zoomtext

# Released under the MIT License. See LICENSE for details.
#
"""Defined Actor(s)."""

from __future__ import annotations

import random
import logging
from typing import TYPE_CHECKING, override

import bascenev1 as bs

if TYPE_CHECKING:
    from typing import Any, Sequence


[docs] class ZoomText(bs.Actor): """Big Zooming Text. Used for things such as the 'BOB WINS' victory messages. """ def __init__( self, text: str | bs.Lstr, position: tuple[float, float] = (0.0, 0.0), *, shiftposition: tuple[float, float] | None = None, shiftdelay: float | None = None, lifespan: float | None = None, flash: bool = True, trail: bool = True, h_align: str = 'center', color: Sequence[float] = (0.9, 0.4, 0.0), jitter: float = 0.0, trailcolor: Sequence[float] = (1.0, 0.35, 0.1, 0.0), scale: float = 1.0, project_scale: float = 1.0, tilt_translate: float = 0.0, maxwidth: float | None = None, ): # pylint: disable=too-many-locals super().__init__() self._dying = False positionadjusted = (position[0], position[1] - 100) if shiftdelay is None: shiftdelay = 2.500 if shiftdelay < 0.0: logging.error('got shiftdelay < 0') shiftdelay = 0.0 self._project_scale = project_scale self.node = bs.newnode( 'text', delegate=self, attrs={ 'position': positionadjusted, 'big': True, 'text': text, 'trail': trail, 'vr_depth': 0, 'shadow': 0.0 if trail else 0.3, 'scale': scale, 'maxwidth': maxwidth if maxwidth is not None else 0.0, 'tilt_translate': tilt_translate, 'h_align': h_align, 'v_align': 'center', }, ) # we never jitter in vr mode.. if bs.app.env.vr: jitter = 0.0 # if they want jitter, animate its position slightly... if jitter > 0.0: self._jitter(positionadjusted, jitter * scale) # if they want shifting, move to the shift position and # then resume jittering if shiftposition is not None: positionadjusted2 = (shiftposition[0], shiftposition[1] - 100) bs.timer( shiftdelay, bs.WeakCall(self._shift, positionadjusted, positionadjusted2), ) if jitter > 0.0: bs.timer( shiftdelay + 0.25, bs.WeakCall( self._jitter, positionadjusted2, jitter * scale ), ) color_combine = bs.newnode( 'combine', owner=self.node, attrs={'input2': color[2], 'input3': 1.0, 'size': 4}, ) if trail: trailcolor_n = bs.newnode( 'combine', owner=self.node, attrs={ 'size': 3, 'input0': trailcolor[0], 'input1': trailcolor[1], 'input2': trailcolor[2], }, ) trailcolor_n.connectattr('output', self.node, 'trailcolor') basemult = 0.85 bs.animate( self.node, 'trail_project_scale', { 0: 0 * project_scale, basemult * 0.201: 0.6 * project_scale, basemult * 0.347: 0.8 * project_scale, basemult * 0.478: 0.9 * project_scale, basemult * 0.595: 0.93 * project_scale, basemult * 0.748: 0.95 * project_scale, basemult * 0.941: 0.95 * project_scale, }, ) if flash: mult = 2.0 tm1 = 0.15 tm2 = 0.3 bs.animate( color_combine, 'input0', {0: color[0] * mult, tm1: color[0], tm2: color[0] * mult}, loop=True, ) bs.animate( color_combine, 'input1', {0: color[1] * mult, tm1: color[1], tm2: color[1] * mult}, loop=True, ) bs.animate( color_combine, 'input2', {0: color[2] * mult, tm1: color[2], tm2: color[2] * mult}, loop=True, ) else: color_combine.input0 = color[0] color_combine.input1 = color[1] color_combine.connectattr('output', self.node, 'color') bs.animate( self.node, 'project_scale', {0: 0, 0.27: 1.05 * project_scale, 0.3: 1 * project_scale}, ) # if they give us a lifespan, kill ourself down the line if lifespan is not None: bs.timer(lifespan, bs.WeakCall(self.handlemessage, bs.DieMessage()))
[docs] @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): if not self._dying and self.node: self._dying = True if msg.immediate: self.node.delete() else: bs.animate( self.node, 'project_scale', { 0.0: 1 * self._project_scale, 0.6: 1.2 * self._project_scale, }, ) bs.animate(self.node, 'opacity', {0.0: 1, 0.3: 0}) bs.animate(self.node, 'trail_opacity', {0.0: 1, 0.6: 0}) bs.timer(0.7, self.node.delete) return None return super().handlemessage(msg)
def _jitter( self, position: tuple[float, float], jitter_amount: float ) -> None: if not self.node: return cmb = bs.newnode('combine', owner=self.node, attrs={'size': 2}) for index, attr in enumerate(['input0', 'input1']): keys = {} timeval = 0.0 # gen some random keys for that stop-motion-y look for _i in range(10): keys[timeval] = ( position[index] + (random.random() - 0.5) * jitter_amount * 1.6 ) timeval += random.random() * 0.1 bs.animate(cmb, attr, keys, loop=True) cmb.connectattr('output', self.node, 'position') def _shift( self, position1: tuple[float, float], position2: tuple[float, float] ) -> None: if not self.node: return cmb = bs.newnode('combine', owner=self.node, attrs={'size': 2}) bs.animate(cmb, 'input0', {0.0: position1[0], 0.25: position2[0]}) bs.animate(cmb, 'input1', {0.0: position1[1], 0.25: position2[1]}) cmb.connectattr('output', self.node, 'position')
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum