mirror of https://github.com/JoshKarpel/spiel
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
3.2 KiB
Python
102 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Iterable, Sequence
|
|
from dataclasses import dataclass
|
|
from functools import cached_property
|
|
from itertools import islice
|
|
from time import monotonic
|
|
from typing import Iterator, TypeVar, overload
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Triggers(Sequence[float]):
|
|
"""
|
|
Provides information to [`Slide.content`][spiel.Slide.content] about the current slide's "trigger state".
|
|
|
|
`Triggers` is a [`Sequence`][collections.abc.Sequence] of times
|
|
(produced by [`time.monotonic`][time.monotonic])
|
|
that the current slide was triggered at.
|
|
Note that the slide will be triggered once when it starts being displayed,
|
|
so the first trigger time will be the time when the slide started being displayed.
|
|
"""
|
|
|
|
now: float
|
|
"""
|
|
The time that the slide content is being rendered at.
|
|
Use this is as a single consistent value to base relative times on.
|
|
"""
|
|
|
|
_times: tuple[float, ...]
|
|
|
|
def __post_init__(self) -> None:
|
|
if not self._times:
|
|
raise ValueError("times must not be empty")
|
|
|
|
if self.now < self._times[-1]:
|
|
raise ValueError(f"now {self.now} must be later than the last time {self._times[-1]}")
|
|
|
|
@classmethod
|
|
def new(self) -> Triggers:
|
|
now = monotonic()
|
|
return Triggers(now=now, _times=(now,))
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._times)
|
|
|
|
@overload
|
|
def __getitem__(self, item: int) -> float:
|
|
return self._times[item]
|
|
|
|
@overload
|
|
def __getitem__(self, item: slice) -> Sequence[float]:
|
|
return self._times[item]
|
|
|
|
def __getitem__(self, item: int | slice) -> float | Sequence[float]:
|
|
return self._times[item]
|
|
|
|
def __iter__(self) -> Iterator[float]:
|
|
return iter(self._times)
|
|
|
|
def __contains__(self, item: object) -> bool:
|
|
return item in self._times
|
|
|
|
@cached_property
|
|
def time_since_last_trigger(self) -> float:
|
|
"""The elapsed time since the most recent trigger."""
|
|
return self.now - self._times[-1]
|
|
|
|
@cached_property
|
|
def time_since_first_trigger(self) -> float:
|
|
"""
|
|
The elapsed time since the first trigger,
|
|
which is equivalent to the time since the slide started being displayed.
|
|
"""
|
|
return self.now - self._times[0]
|
|
|
|
@cached_property
|
|
def triggered(self) -> bool:
|
|
"""
|
|
Returns whether the slide has been *manually* triggered
|
|
(i.e., this ignores the initial trigger from when the slide starts being displayed).
|
|
"""
|
|
return len(self) > 1
|
|
|
|
def take(self, iter: Iterable[T], offset: int = 1) -> Iterator[T]:
|
|
"""
|
|
Takes elements from the iterable `iter`
|
|
equal to the number of times in the `Triggers` minus the offset.
|
|
|
|
Args:
|
|
iter: The iterable to take elements from.
|
|
offset: This `offset` will be subtracted from the number of triggers,
|
|
reducing the number of elements that will be returned.
|
|
It defaults to `1` to ignore the automatic trigger from when the
|
|
slide starts being shown.
|
|
|
|
Returns:
|
|
An iterator over the first `len(self) - offset` elements of `iter`.
|
|
"""
|
|
return islice(iter, len(self) - offset)
|