Source code for neoscore.interface.path_interface

from __future__ import annotations

from dataclasses import dataclass
from typing import List, NamedTuple, Optional, Union

from PyQt5.QtGui import QPainterPath
from typing_extensions import TypeAlias

from neoscore.core.units import Unit
from neoscore.interface.brush_interface import BrushInterface
from neoscore.interface.pen_interface import PenInterface
from neoscore.interface.positioned_object_interface import PositionedObjectInterface
from neoscore.interface.qt.converters import point_to_qt_point_f
from neoscore.interface.qt.q_clipping_path import QClippingPath


[docs]class ResolvedMoveTo(NamedTuple): """A canvas-space move-to element.""" # Add blank docstrings to suppress ugly default namedtuple docstring used by Sphinx x: Unit """""" y: Unit """"""
[docs]class ResolvedLineTo(NamedTuple): """A canvas-space line-to element.""" x: Unit """""" y: Unit """"""
[docs]class ResolvedCurveTo(NamedTuple): """A canvas-space curve-to element.""" c1_x: Unit """""" c1_y: Unit """""" c2_x: Unit """""" c2_y: Unit """""" end_x: Unit """""" end_y: Unit """"""
ResolvedPathElement: TypeAlias = Union[ResolvedMoveTo, ResolvedLineTo, ResolvedCurveTo] """A path element whose position is relative to its path"""
[docs]@dataclass(frozen=True) class PathInterface(PositionedObjectInterface): """Interface for a generic graphic path object.""" brush: BrushInterface pen: PenInterface elements: List[ResolvedPathElement] background_brush: Optional[BrushInterface] = None clip_start_x: Optional[Unit] = None """The local starting position of the drawn region in the glyph. Use ``None`` to render from the start """ clip_width: Optional[Unit] = None """The width of the visible region. Use ``None`` to render to the end. """
[docs] @staticmethod def create_qt_path(elements: List[ResolvedPathElement]) -> QPainterPath: path = QPainterPath() path.setFillRule(1) for el in elements: if isinstance(el, ResolvedLineTo): path.lineTo(el.x.base_value, el.y.base_value) elif isinstance(el, ResolvedMoveTo): path.moveTo(el.x.base_value, el.y.base_value) elif isinstance(el, ResolvedCurveTo): path.cubicTo( el.c1_x.base_value, el.c1_y.base_value, el.c2_x.base_value, el.c2_y.base_value, el.end_x.base_value, el.end_y.base_value, ) else: raise TypeError("Unknown ResolvedPathElement type") return path
[docs] def render(self): """Render the path to the scene.""" self._register_qt_object(self._create_qt_object())
def _create_qt_object(self) -> QClippingPath: painter_path = PathInterface.create_qt_path(self.elements) qt_object = QClippingPath( painter_path, self.clip_start_x.base_value if self.clip_start_x is not None else 0, self.clip_width.base_value if self.clip_width is not None else None, self.scale, self.rotation, self.background_brush.qt_object if self.background_brush else None, defer_geometry_calculation=True, transform_origin=point_to_qt_point_f(self.transform_origin), ) qt_object.setPos(point_to_qt_point_f(self.pos)) qt_object.setBrush(self.brush.qt_object) qt_object.setPen(self.pen.qt_object) qt_object.update_geometry() return qt_object