Source code for gaphas.connector

"""Basic connectors such as Ports and Handles."""
from __future__ import annotations

from typing import TYPE_CHECKING, Tuple, Union

from gaphas.constraint import Constraint, LineConstraint, PositionConstraint
from gaphas.geometry import distance_line_point, distance_point_point
from gaphas.position import MatrixProjection, Position
from gaphas.solver import NORMAL, MultiConstraint
from gaphas.types import Pos, SupportsFloatPos, TypedProperty

if TYPE_CHECKING:
    from gaphas.item import Item


[docs]class Handle: """Handles are used to support modifications of Items. If the handle is connected to an item, the ``connected_to`` property should refer to the item. A ``disconnect`` handler should be provided that handles all disconnect behaviour (e.g. clean up constraints and ``connected_to``). Note for those of you that use the Pickle module to persist a canvas: The property ``disconnect`` should contain a callable object (with __call__() method), so the pickle handler can also pickle that. Pickle is not capable of pickling ``instancemethod`` or ``function`` objects. """ def __init__( self, pos: Pos = (0, 0), strength: int = NORMAL, connectable: bool = False, movable: bool = True, ) -> None: """Create a new handle. Position is in item coordinates. """ self._pos = Position(pos[0], pos[1], strength) self._connectable = connectable self._movable = movable self._visible = True def _set_pos(self, pos: Union[Position, SupportsFloatPos]) -> None: """ Shortcut for ``handle.pos.pos = pos`` >>> h = Handle((10, 10)) >>> h.pos = (20, 15) >>> h.pos <Position object on (20, 15)> """ self._pos.pos = pos pos: TypedProperty[Position, Union[Position, SupportsFloatPos]] pos = property(lambda s: s._pos, _set_pos, doc="The Handle's position") def _set_connectable(self, connectable: bool) -> None: self._connectable = connectable connectable = property( lambda s: s._connectable, _set_connectable, doc="Can this handle actually connectect to a port?", ) def _set_movable(self, movable: bool) -> None: self._movable = movable movable = property( lambda s: s._movable, _set_movable, doc="Can this handle be moved by a mouse pointer?", ) def _set_visible(self, visible: bool) -> None: self._visible = visible visible = property( lambda s: s._visible, _set_visible, doc="Is this handle visible to the user?" ) def __str__(self) -> str: return f"<{self.__class__.__name__} object on ({self._pos.x}, {self._pos.y})>" __repr__ = __str__
[docs]class Port: """Port connectable part of an item. The Item's handle connects to a port. """ def __init__(self) -> None: super().__init__() self._connectable = True def _set_connectable(self, connectable: bool) -> None: self._connectable = connectable connectable = property(lambda s: s._connectable, _set_connectable)
[docs] def glue(self, pos: SupportsFloatPos) -> Tuple[Pos, float]: """Get glue point on the port and distance to the port.""" raise NotImplementedError("Glue method not implemented")
[docs] def constraint(self, item: Item, handle: Handle, glue_item: Item) -> Constraint: """Create connection constraint between item's handle and glue item.""" raise NotImplementedError("Constraint method not implemented")
[docs]class LinePort(Port): """Port defined as a line between two handles.""" def __init__(self, start: Position, end: Position) -> None: super().__init__() self.start = start self.end = end
[docs] def glue(self, pos: SupportsFloatPos) -> Tuple[Pos, float]: """Get glue point on the port and distance to the port. >>> p1, p2 = (0.0, 0.0), (100.0, 100.0) >>> port = LinePort(p1, p2) >>> port.glue((50, 50)) ((50.0, 50.0), 0.0) >>> port.glue((0, 10)) ((5.0, 5.0), 7.0710678118654755) """ d, pl = distance_line_point( self.start.tuple(), self.end.tuple(), (float(pos[0]), float(pos[1])) ) return pl, d
[docs] def constraint(self, item: Item, handle: Handle, glue_item: Item) -> Constraint: """Create connection line constraint between item's handle and the port.""" start = MatrixProjection(self.start, glue_item.matrix_i2c) end = MatrixProjection(self.end, glue_item.matrix_i2c) point = MatrixProjection(handle.pos, item.matrix_i2c) line = LineConstraint((start.pos, end.pos), point.pos) return MultiConstraint(start, end, point, line)
[docs]class PointPort(Port): """Port defined as a point.""" def __init__(self, point: Position) -> None: super().__init__() self.point = point
[docs] def glue(self, pos: SupportsFloatPos) -> Tuple[Pos, float]: """Get glue point on the port and distance to the port. >>> h = Handle((10, 10)) >>> port = PointPort(h.pos) >>> port.glue((10, 0)) (<Position object on (10, 10)>, 10.0) """ point: Tuple[float, float] = self.point.pos # type: ignore[assignment] d = distance_point_point(point, (float(pos[0]), float(pos[1]))) return point, d
[docs] def constraint( self, item: Item, handle: Handle, glue_item: Item ) -> MultiConstraint: """Return connection position constraint between item's handle and the port.""" origin = MatrixProjection(self.point, glue_item.matrix_i2c) point = MatrixProjection(handle.pos, item.matrix_i2c) c = PositionConstraint(origin.pos, point.pos) return MultiConstraint(origin, point, c)