Source code for neoscore.core.spanner

from __future__ import annotations

from typing import cast

from neoscore.core.point import Point
from neoscore.core.positioned_object import PositionedObject, render_cached_property
from neoscore.core.units import Unit


[docs]class Spanner: """Mixin for a :obj:`.PositionedObject` with starting and ending anchors. If the spanner is in a :obj:`.Flowable`, the endpoint must be in the same one. Likewise, if the spanner is *not* in one, the endpoint must not be in one either. This mixin only provides a common interface for ending anchors. The starting position of this spanner should be the main object's ``PositionedObject.pos``, and the starting anchor should be its ``PositionedObject.parent``. It is up to the implementing class to decide how to use this information. Simple ``Spanner``\ s are horizontal relative to their starting anchor. Arbitrary end-y positions can be set with :obj:`.Spanner2D`. Implementations should be careful to declare ``Spanner`` as a superclass **before** ``PositionedObject`` or any inheritors of it. This is necessary in order to get the correct method resolution order for custom ``PositionedObject`` behavior provided by this class - namely :obj:`.Spanner.breakable_length`. """
[docs] def __init__(self, end_x: Unit, end_parent: PositionedObject): """ Args: end_x: The X position of the endpoint. end_parent: The parent of the endpoint. ``end_x`` will be relative to this object. This can be the spanner itself to make it relative to the starting point. """ self._end_x = end_x self._end_parent = end_parent
@property def end_x(self) -> Unit: """The x position of the endpoint""" return self._end_x @end_x.setter def end_x(self, value: Unit): self._end_x = value @render_cached_property def end_y(self) -> Unit: """The y position of the endpoint. This value is automatically computed such that the spanner is horizontal. """ return self.end_parent.map_to(cast(PositionedObject, self)).y @property def end_pos(self) -> Point: """The position of the endpoint""" return Point(self.end_x, self.end_y) @property def end_parent(self) -> PositionedObject: """The parent of the endpoint. This may be ``self``.""" return self._end_parent @end_parent.setter def end_parent(self, value: PositionedObject): self._end_parent = value @render_cached_property def spanner_x_length(self) -> Unit: """The x-axis length of the spanner.""" if self.end_parent == self: return self.end_pos.x else: return ( cast(PositionedObject, self).map_x_to(self.end_parent) + self.end_pos.x ) @property def breakable_length(self) -> Unit: """Spanners are breakable over their ``spanner_x_length``. Note that in order for this implementation to take effect on concrete spanners, ``Spanner`` must be declared as a superclass *before* ``PositionedObject`` or any other class that implements ``breakable_length``! """ return self.spanner_x_length
[docs] def point_along_spanner(self, ratio: float) -> Point: """Find the point on the spanner at a given 0-1 ratio along it. For example, a ratio of ``0.5`` will give the point half-way along the spanner. The returned point will be relative to the starting point. Args: ratio: A value representing progress along the spanner, where 0 is at the start and 1 is at the end. Values outside these bounds will give a point as if the spanner extended to infinity. """ return ( cast(PositionedObject, self).map_to(self.end_parent) + self.end_pos ) * ratio