# Released under the MIT License. See LICENSE for details.
#
"""Defines Actor(s)."""
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING, override
import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
[docs]
class Text(bs.Actor):
"""Text with some tricks."""
[docs]
class Transition(Enum):
"""Transition types for text."""
FADE_IN = 'fade_in'
IN_RIGHT = 'in_right'
IN_LEFT = 'in_left'
IN_BOTTOM = 'in_bottom'
IN_BOTTOM_SLOW = 'in_bottom_slow'
IN_TOP_SLOW = 'in_top_slow'
[docs]
class HAlign(Enum):
"""Horizontal alignment type."""
LEFT = 'left'
CENTER = 'center'
RIGHT = 'right'
[docs]
class VAlign(Enum):
"""Vertical alignment type."""
NONE = 'none'
CENTER = 'center'
[docs]
class HAttach(Enum):
"""Horizontal attach type."""
LEFT = 'left'
CENTER = 'center'
RIGHT = 'right'
[docs]
class VAttach(Enum):
"""Vertical attach type."""
BOTTOM = 'bottom'
CENTER = 'center'
TOP = 'top'
def __init__(
self,
text: str | bs.Lstr,
*,
position: tuple[float, float] = (0.0, 0.0),
h_align: HAlign = HAlign.LEFT,
v_align: VAlign = VAlign.NONE,
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
transition: Transition | None = None,
transition_delay: float = 0.0,
flash: bool = False,
v_attach: VAttach = VAttach.CENTER,
h_attach: HAttach = HAttach.CENTER,
scale: float = 1.0,
transition_out_delay: float | None = None,
maxwidth: float | None = None,
shadow: float = 0.5,
flatness: float = 0.0,
vr_depth: float = 0.0,
host_only: bool = False,
front: bool = False,
):
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
super().__init__()
self.node = bs.newnode(
'text',
delegate=self,
attrs={
'text': text,
'color': color,
'position': position,
'h_align': h_align.value,
'vr_depth': vr_depth,
'v_align': v_align.value,
'h_attach': h_attach.value,
'v_attach': v_attach.value,
'shadow': shadow,
'flatness': flatness,
'maxwidth': 0.0 if maxwidth is None else maxwidth,
'host_only': host_only,
'front': front,
'scale': scale,
},
)
if transition is self.Transition.FADE_IN:
if flash:
raise RuntimeError(
'fixme: flash and fade-in currently cant both be on'
)
cmb = bs.newnode(
'combine',
owner=self.node,
attrs={
'input0': color[0],
'input1': color[1],
'input2': color[2],
'size': 4,
},
)
keys = {transition_delay: 0.0, transition_delay + 0.5: color[3]}
if transition_out_delay is not None:
keys[transition_delay + transition_out_delay] = color[3]
keys[transition_delay + transition_out_delay + 0.5] = 0.0
bs.animate(cmb, 'input3', keys)
cmb.connectattr('output', self.node, 'color')
if flash:
mult = 2.0
tm1 = 0.15
tm2 = 0.3
cmb = bs.newnode('combine', owner=self.node, attrs={'size': 4})
bs.animate(
cmb,
'input0',
{0.0: color[0] * mult, tm1: color[0], tm2: color[0] * mult},
loop=True,
)
bs.animate(
cmb,
'input1',
{0.0: color[1] * mult, tm1: color[1], tm2: color[1] * mult},
loop=True,
)
bs.animate(
cmb,
'input2',
{0.0: color[2] * mult, tm1: color[2], tm2: color[2] * mult},
loop=True,
)
cmb.input3 = color[3]
cmb.connectattr('output', self.node, 'color')
cmb = self.position_combine = bs.newnode(
'combine', owner=self.node, attrs={'size': 2}
)
if transition is self.Transition.IN_RIGHT:
keys = {
transition_delay: position[0] + 1300,
transition_delay + 0.2: position[0],
}
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
bs.animate(cmb, 'input0', keys)
cmb.input1 = position[1]
bs.animate(self.node, 'opacity', o_keys)
elif transition is self.Transition.IN_LEFT:
keys = {
transition_delay: position[0] - 1300,
transition_delay + 0.2: position[0],
}
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
if transition_out_delay is not None:
keys[transition_delay + transition_out_delay] = position[0]
keys[transition_delay + transition_out_delay + 0.2] = (
position[0] - 1300.0
)
o_keys[transition_delay + transition_out_delay + 0.15] = 1.0
o_keys[transition_delay + transition_out_delay + 0.2] = 0.0
bs.animate(cmb, 'input0', keys)
cmb.input1 = position[1]
bs.animate(self.node, 'opacity', o_keys)
elif transition is self.Transition.IN_BOTTOM_SLOW:
keys = {
transition_delay: -100.0,
transition_delay + 1.0: position[1],
}
o_keys = {transition_delay: 0.0, transition_delay + 0.2: 1.0}
cmb.input0 = position[0]
bs.animate(cmb, 'input1', keys)
bs.animate(self.node, 'opacity', o_keys)
elif transition is self.Transition.IN_BOTTOM:
keys = {
transition_delay: -100.0,
transition_delay + 0.2: position[1],
}
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
if transition_out_delay is not None:
keys[transition_delay + transition_out_delay] = position[1]
keys[transition_delay + transition_out_delay + 0.2] = -100.0
o_keys[transition_delay + transition_out_delay + 0.15] = 1.0
o_keys[transition_delay + transition_out_delay + 0.2] = 0.0
cmb.input0 = position[0]
bs.animate(cmb, 'input1', keys)
bs.animate(self.node, 'opacity', o_keys)
elif transition is self.Transition.IN_TOP_SLOW:
keys = {
transition_delay: 400.0,
transition_delay + 3.5: position[1],
}
o_keys = {transition_delay: 0, transition_delay + 1.0: 1.0}
cmb.input0 = position[0]
bs.animate(cmb, 'input1', keys)
bs.animate(self.node, 'opacity', o_keys)
else:
assert transition is self.Transition.FADE_IN or transition is None
cmb.input0 = position[0]
cmb.input1 = position[1]
cmb.connectattr('output', self.node, 'position')
# If we're transitioning out, die at the end of it.
if transition_out_delay is not None:
bs.timer(
transition_delay + transition_out_delay + 1.0,
bs.WeakCall(self.handlemessage, bs.DieMessage()),
)
[docs]
@override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
if self.node:
self.node.delete()
return None
return super().handlemessage(msg)
# 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