Turn Item into a protocol
Now we do not need to inherit from Item explicitly.
This commit is contained in:
parent
4b237b8ecd
commit
3dd5d02721
@ -23,9 +23,9 @@ The observer simply dispatches the function called (as ``<function ..>``, not as
|
|||||||
Let's start with creating a Canvas instance and some items:
|
Let's start with creating a Canvas instance and some items:
|
||||||
|
|
||||||
>>> from gaphas.canvas import Canvas
|
>>> from gaphas.canvas import Canvas
|
||||||
>>> from gaphas.item import Item
|
>>> from examples.exampleitems import Circle
|
||||||
>>> canvas = Canvas()
|
>>> canvas = Canvas()
|
||||||
>>> item1, item2 = Item(), Item()
|
>>> item1, item2 = Circle(), Circle()
|
||||||
|
|
||||||
For this demonstration let's use the Canvas class (which contains an add/remove
|
For this demonstration let's use the Canvas class (which contains an add/remove
|
||||||
method pair).
|
method pair).
|
||||||
@ -36,11 +36,11 @@ It works (see how the add method automatically schedules the item for update):
|
|||||||
... print('event handled', event)
|
... print('event handled', event)
|
||||||
>>> state.observers.add(handler)
|
>>> state.observers.add(handler)
|
||||||
>>> canvas.add(item1) # doctest: +ELLIPSIS
|
>>> canvas.add(item1) # doctest: +ELLIPSIS
|
||||||
event handled (<function Canvas.add at ...>, (<gaphas.canvas.Canvas object at ...>, <gaphas.item.Item object at ...>), {})
|
event handled (<function Canvas.add at ...>, (<gaphas.canvas.Canvas object at ...>, <examples.exampleitems.Circle object at ...>), {})
|
||||||
>>> canvas.add(item2, parent=item1) # doctest: +ELLIPSIS
|
>>> canvas.add(item2, parent=item1) # doctest: +ELLIPSIS
|
||||||
event handled (<function Canvas.add at ...>, (<gaphas.canvas.Canvas object at ...>, <gaphas.item.Item object at ...>), {'parent': <gaphas.item.Item object at ...>})
|
event handled (<function Canvas.add at ...>, (<gaphas.canvas.Canvas object at ...>, <examples.exampleitems.Circle object at ...>), {'parent': <examples.exampleitems.Circle object at ...>})
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||||
|
|
||||||
Note that the handler is invoked before the actual call is made. This is
|
Note that the handler is invoked before the actual call is made. This is
|
||||||
important if you want to store the (old) state for an undo mechanism.
|
important if you want to store the (old) state for an undo mechanism.
|
||||||
@ -52,8 +52,8 @@ Therefore some careful crafting of methods may be necessary in order to get the
|
|||||||
right effect (items should be removed in the right order, child first):
|
right effect (items should be removed in the right order, child first):
|
||||||
|
|
||||||
>>> canvas.remove(item1) # doctest: +ELLIPSIS
|
>>> canvas.remove(item1) # doctest: +ELLIPSIS
|
||||||
event handled (<function Canvas._remove at ...>, (<gaphas.canvas.Canvas object at 0x...>, <gaphas.item.Item object at 0x...>), {})
|
event handled (<function Canvas._remove at ...>, (<gaphas.canvas.Canvas object at 0x...>, <examples.exampleitems.Circle object at 0x...>), {})
|
||||||
event handled (<function Canvas._remove at ...>, (<gaphas.canvas.Canvas object at 0x...>, <gaphas.item.Item object at 0x...>), {})
|
event handled (<function Canvas._remove at ...>, (<gaphas.canvas.Canvas object at 0x...>, <examples.exampleitems.Circle object at 0x...>), {})
|
||||||
>>> list(canvas.get_all_items())
|
>>> list(canvas.get_all_items())
|
||||||
[]
|
[]
|
||||||
|
|
||||||
@ -126,10 +126,11 @@ Handlers for the reverse events should be registered on the subscribers list:
|
|||||||
|
|
||||||
After that, signals can be received of undoable (reverse-)events:
|
After that, signals can be received of undoable (reverse-)events:
|
||||||
|
|
||||||
>>> canvas.add(Item()) # doctest: +ELLIPSIS
|
>>> canvas.add(Circle()) # doctest: +ELLIPSIS
|
||||||
event handler (<function Canvas._remove at ...>, {'self': <gaphas.canvas.Canvas object at 0x...>, 'item': <gaphas.item.Item object at 0x...>})
|
event handler (<function Handle._set_movable at ...>, {'self': <Handle object on (Variable(0, 20), Variable(0, 20))>, 'movable': True})
|
||||||
|
event handler (<function Canvas._remove at ...>, {'self': <gaphas.canvas.Canvas object at 0x...>, 'item': <examples.exampleitems.Circle object at 0x...>})
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>]
|
||||||
|
|
||||||
As you can see this event is constructed of only two parameters: the function
|
As you can see this event is constructed of only two parameters: the function
|
||||||
that does the inverse operation of ``add()`` and the arguments that should be
|
that does the inverse operation of ``add()`` and the arguments that should be
|
||||||
@ -139,7 +140,7 @@ The inverse operation is easiest performed by the function ``saveapply()``. Of
|
|||||||
course an inverse operation is emitting a change event too:
|
course an inverse operation is emitting a change event too:
|
||||||
|
|
||||||
>>> state.saveapply(*events.pop()) # doctest: +ELLIPSIS
|
>>> state.saveapply(*events.pop()) # doctest: +ELLIPSIS
|
||||||
event handler (<function Canvas.add at 0x...>, {'self': <gaphas.canvas.Canvas object at 0x...>, 'item': <gaphas.item.Item object at 0x...>, 'parent': None, 'index': 0})
|
event handler (<function Canvas.add at 0x...>, {'self': <gaphas.canvas.Canvas object at 0x...>, 'item': <examples.exampleitems.Circle object at 0x...>, 'parent': None, 'index': 0})
|
||||||
>>> list(canvas.get_all_items())
|
>>> list(canvas.get_all_items())
|
||||||
[]
|
[]
|
||||||
|
|
||||||
|
@ -111,25 +111,26 @@ Again, rotate does not result in an exact match, but it's close enough.
|
|||||||
canvas.py: Canvas
|
canvas.py: Canvas
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
>>> from gaphas import Canvas, Item
|
>>> from gaphas import Canvas
|
||||||
|
>>> from examples.exampleitems import Circle
|
||||||
>>> canvas = Canvas()
|
>>> canvas = Canvas()
|
||||||
>>> list(canvas.get_all_items())
|
>>> list(canvas.get_all_items())
|
||||||
[]
|
[]
|
||||||
>>> item = Item()
|
>>> item = Circle()
|
||||||
>>> canvas.add(item)
|
>>> canvas.add(item)
|
||||||
|
|
||||||
The ``request_update()`` method is observed:
|
The ``request_update()`` method is observed:
|
||||||
|
|
||||||
>>> len(undo_list)
|
>>> len(undo_list)
|
||||||
1
|
2
|
||||||
>>> canvas.request_update(item)
|
>>> canvas.request_update(item)
|
||||||
>>> len(undo_list)
|
>>> len(undo_list)
|
||||||
2
|
3
|
||||||
|
|
||||||
On the canvas only ``add()`` and ``remove()`` are monitored:
|
On the canvas only ``add()`` and ``remove()`` are monitored:
|
||||||
|
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>]
|
||||||
>>> undo()
|
>>> undo()
|
||||||
>>> list(canvas.get_all_items())
|
>>> list(canvas.get_all_items())
|
||||||
[]
|
[]
|
||||||
@ -140,7 +141,7 @@ On the canvas only ``add()`` and ``remove()`` are monitored:
|
|||||||
[]
|
[]
|
||||||
>>> undo()
|
>>> undo()
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>]
|
||||||
>>> undo_list
|
>>> undo_list
|
||||||
[]
|
[]
|
||||||
|
|
||||||
@ -149,15 +150,15 @@ Parent-child relationships are restored as well:
|
|||||||
TODO!
|
TODO!
|
||||||
|
|
||||||
|
|
||||||
>>> child = Item()
|
>>> child = Circle()
|
||||||
>>> canvas.add(child, parent=item)
|
>>> canvas.add(child, parent=item)
|
||||||
>>> canvas.get_parent(child) is item
|
>>> canvas.get_parent(child) is item
|
||||||
True
|
True
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||||
>>> undo()
|
>>> undo()
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>]
|
||||||
>>> child in canvas.get_all_items()
|
>>> child in canvas.get_all_items()
|
||||||
False
|
False
|
||||||
|
|
||||||
@ -168,32 +169,35 @@ Now redo the previous undo action:
|
|||||||
>>> canvas.get_parent(child) is item
|
>>> canvas.get_parent(child) is item
|
||||||
True
|
True
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||||
|
|
||||||
Remove also works when items are removed recursively (an item and it's
|
Remove also works when items are removed recursively (an item and it's
|
||||||
children):
|
children):
|
||||||
|
|
||||||
>>> child = Item()
|
>>> child = Circle()
|
||||||
>>> canvas.add(child, parent=item)
|
>>> canvas.add(child, parent=item)
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||||
>>> del undo_list[:]
|
>>> del undo_list[:]
|
||||||
>>> canvas.remove(item)
|
>>> canvas.remove(item)
|
||||||
>>> list(canvas.get_all_items())
|
>>> list(canvas.get_all_items())
|
||||||
[]
|
[]
|
||||||
>>> undo()
|
>>> undo()
|
||||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>, <gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||||
>>> canvas.get_children(item) # doctest: +ELLIPSIS
|
>>> canvas.get_children(item) # doctest: +ELLIPSIS
|
||||||
[<gaphas.item.Item object at 0x...>]
|
[<examples.exampleitems.Circle object at 0x...>]
|
||||||
|
|
||||||
As well as the reparent() method:
|
As well as the reparent() method:
|
||||||
|
|
||||||
>>> canvas = Canvas()
|
>>> canvas = Canvas()
|
||||||
>>> class NameItem(Item):
|
>>> class NameItem:
|
||||||
... def __init__(self, name):
|
... def __init__(self, name):
|
||||||
... super(NameItem, self).__init__()
|
... super(NameItem, self).__init__()
|
||||||
... self.name = name
|
... self.name = name
|
||||||
|
... def handles(self): return []
|
||||||
|
... def ports(self): return []
|
||||||
|
... def point(self, x, y): return 0
|
||||||
... def __repr__(self):
|
... def __repr__(self):
|
||||||
... return '<%s>' % self.name
|
... return '<%s>' % self.name
|
||||||
>>> ni1 = NameItem('a')
|
>>> ni1 = NameItem('a')
|
||||||
@ -347,9 +351,9 @@ Also creation and removal of connected lines is recorded and can be undone:
|
|||||||
... def real_disconnect():
|
... def real_disconnect():
|
||||||
... pass
|
... pass
|
||||||
... canvas.connections.connect_item(hitem, handle, item, port=None, constraint=None, callback=real_disconnect)
|
... canvas.connections.connect_item(hitem, handle, item, port=None, constraint=None, callback=real_disconnect)
|
||||||
>>> b0 = Item()
|
>>> b0 = Circle()
|
||||||
>>> canvas.add(b0)
|
>>> canvas.add(b0)
|
||||||
>>> b1 = Item()
|
>>> b1 = Circle()
|
||||||
>>> canvas.add(b1)
|
>>> canvas.add(b1)
|
||||||
>>> l = Line(Connections())
|
>>> l = Line(Connections())
|
||||||
>>> canvas.add(l)
|
>>> canvas.add(l)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
These items are used in various tests.
|
These items are used in various tests.
|
||||||
"""
|
"""
|
||||||
from gaphas.connector import Handle
|
from gaphas.connector import Handle
|
||||||
from gaphas.item import NW, Element, Item
|
from gaphas.item import NW, Element, Matrices, Updateable
|
||||||
from gaphas.util import path_ellipse, text_align, text_multiline
|
from gaphas.util import path_ellipse, text_align, text_multiline
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ class Box(Element):
|
|||||||
c.stroke()
|
c.stroke()
|
||||||
|
|
||||||
|
|
||||||
class Text(Item):
|
class Text(Matrices, Updateable):
|
||||||
"""Simple item showing some text on the canvas."""
|
"""Simple item showing some text on the canvas."""
|
||||||
|
|
||||||
def __init__(self, text=None, plain=False, multiline=False, align_x=1, align_y=-1):
|
def __init__(self, text=None, plain=False, multiline=False, align_x=1, align_y=-1):
|
||||||
@ -40,6 +40,15 @@ class Text(Item):
|
|||||||
self.align_x = align_x
|
self.align_x = align_x
|
||||||
self.align_y = align_y
|
self.align_y = align_y
|
||||||
|
|
||||||
|
def handles(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def ports(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def point(self, x, y):
|
||||||
|
return 0
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
cr = context.cairo
|
cr = context.cairo
|
||||||
if self.multiline:
|
if self.multiline:
|
||||||
@ -49,14 +58,11 @@ class Text(Item):
|
|||||||
else:
|
else:
|
||||||
text_align(cr, 0, 0, self.text, self.align_x, self.align_y)
|
text_align(cr, 0, 0, self.text, self.align_x, self.align_y)
|
||||||
|
|
||||||
def point(self, x, y):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
class Circle(Matrices, Updateable):
|
||||||
class Circle(Item):
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._handles.extend((Handle(), Handle()))
|
self._handles = [Handle(), Handle()]
|
||||||
h1, h2 = self._handles
|
h1, h2 = self._handles
|
||||||
h1.movable = False
|
h1.movable = False
|
||||||
|
|
||||||
@ -72,6 +78,12 @@ class Circle(Item):
|
|||||||
|
|
||||||
radius = property(_get_radius, _set_radius)
|
radius = property(_get_radius, _set_radius)
|
||||||
|
|
||||||
|
def handles(self):
|
||||||
|
return self._handles
|
||||||
|
|
||||||
|
def ports(self):
|
||||||
|
return []
|
||||||
|
|
||||||
def point(self, x, y):
|
def point(self, x, y):
|
||||||
h1, _ = self._handles
|
h1, _ = self._handles
|
||||||
p1 = h1.pos
|
p1 = h1.pos
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Basic items."""
|
"""Basic items."""
|
||||||
from math import atan2
|
from math import atan2
|
||||||
from typing import Sequence
|
from typing import Protocol, Sequence
|
||||||
|
|
||||||
from gaphas.canvas import Context
|
from gaphas.canvas import Context
|
||||||
from gaphas.connector import Handle, LinePort, Port
|
from gaphas.connector import Handle, LinePort, Port
|
||||||
@ -16,31 +16,48 @@ from gaphas.state import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Item(Protocol):
|
||||||
|
@property
|
||||||
|
def matrix(self) -> Matrix:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def matrix_i2c(self) -> Matrix:
|
||||||
|
...
|
||||||
|
|
||||||
|
def handles(self) -> Sequence[Handle]:
|
||||||
|
"""Return a list of handles owned by the item."""
|
||||||
|
|
||||||
|
def ports(self) -> Sequence[Port]:
|
||||||
|
"""Return list of ports."""
|
||||||
|
|
||||||
|
def point(self, x: float, y: float) -> float:
|
||||||
|
"""Get the distance from a point (``x``, ``y``) to the item.
|
||||||
|
|
||||||
|
``x`` and ``y`` are in item coordinates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def draw(self, context: Context):
|
||||||
|
"""Render the item to a canvas view. Context contains the following
|
||||||
|
attributes:
|
||||||
|
|
||||||
|
- cairo: the Cairo Context use this one to draw
|
||||||
|
- selected, focused, hovered, dropzone: view state of items
|
||||||
|
(True/False)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def matrix_i2i(from_item, to_item):
|
def matrix_i2i(from_item, to_item):
|
||||||
i2c = from_item.matrix_i2c
|
i2c = from_item.matrix_i2c
|
||||||
c2i = to_item.matrix_i2c.inverse()
|
c2i = to_item.matrix_i2c.inverse()
|
||||||
return i2c.multiply(c2i)
|
return i2c.multiply(c2i)
|
||||||
|
|
||||||
|
|
||||||
class Item:
|
class Matrices:
|
||||||
"""Base class (or interface) for items on a canvas.Canvas.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
|
|
||||||
- matrix: item's transformation matrix
|
|
||||||
|
|
||||||
Private:
|
|
||||||
|
|
||||||
- _handles: list of handles owned by an item
|
|
||||||
- _ports: list of ports, connectable areas of an item
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs) # type: ignore[call-arg]
|
super().__init__(**kwargs) # type: ignore[call-arg]
|
||||||
self._matrix = Matrix()
|
self._matrix = Matrix()
|
||||||
self._matrix_i2c = Matrix()
|
self._matrix_i2c = Matrix()
|
||||||
self._handles = []
|
|
||||||
self._ports = []
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def matrix(self) -> Matrix:
|
def matrix(self) -> Matrix:
|
||||||
@ -50,6 +67,8 @@ class Item:
|
|||||||
def matrix_i2c(self) -> Matrix:
|
def matrix_i2c(self) -> Matrix:
|
||||||
return self._matrix_i2c
|
return self._matrix_i2c
|
||||||
|
|
||||||
|
|
||||||
|
class Updateable:
|
||||||
def pre_update(self, context: Context):
|
def pre_update(self, context: Context):
|
||||||
"""Perform any changes before item update here, for example:
|
"""Perform any changes before item update here, for example:
|
||||||
|
|
||||||
@ -76,36 +95,11 @@ class Item:
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def draw(self, context: Context):
|
|
||||||
"""Render the item to a canvas view. Context contains the following
|
|
||||||
attributes:
|
|
||||||
|
|
||||||
- cairo: the Cairo Context use this one to draw
|
|
||||||
- selected, focused, hovered, dropzone: view state of items
|
|
||||||
(True/False)
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handles(self) -> Sequence[Handle]:
|
|
||||||
"""Return a list of handles owned by the item."""
|
|
||||||
return self._handles
|
|
||||||
|
|
||||||
def ports(self) -> Sequence[Port]:
|
|
||||||
"""Return list of ports."""
|
|
||||||
return self._ports
|
|
||||||
|
|
||||||
def point(self, x: float, y: float):
|
|
||||||
"""Get the distance from a point (``x``, ``y``) to the item.
|
|
||||||
|
|
||||||
``x`` and ``y`` are in item coordinates.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
[NW, NE, SE, SW] = list(range(4))
|
[NW, NE, SE, SW] = list(range(4))
|
||||||
|
|
||||||
|
|
||||||
class Element(Item):
|
class Element(Matrices, Updateable):
|
||||||
"""An Element has 4 handles (for a start)::
|
"""An Element has 4 handles (for a start)::
|
||||||
|
|
||||||
NW +---+ NE | | SW +---+ SE
|
NW +---+ NE | | SW +---+ SE
|
||||||
@ -199,6 +193,14 @@ class Element(Item):
|
|||||||
|
|
||||||
height = property(_get_height, _set_height)
|
height = property(_get_height, _set_height)
|
||||||
|
|
||||||
|
def handles(self) -> Sequence[Handle]:
|
||||||
|
"""Return a list of handles owned by the item."""
|
||||||
|
return self._handles
|
||||||
|
|
||||||
|
def ports(self) -> Sequence[Port]:
|
||||||
|
"""Return list of ports."""
|
||||||
|
return self._ports
|
||||||
|
|
||||||
def point(self, x, y):
|
def point(self, x, y):
|
||||||
"""Distance from the point (x, y) to the item.
|
"""Distance from the point (x, y) to the item.
|
||||||
|
|
||||||
@ -212,6 +214,9 @@ class Element(Item):
|
|||||||
list(map(float, (pnw.x, pnw.y, pse.x, pse.y))), (x, y)
|
list(map(float, (pnw.x, pnw.y, pse.x, pse.y))), (x, y)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def draw(self, context: Context):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def create_orthogonal_constraints(handles, horizontal):
|
def create_orthogonal_constraints(handles, horizontal):
|
||||||
rest = 1 if horizontal else 0
|
rest = 1 if horizontal else 0
|
||||||
@ -224,7 +229,7 @@ def create_orthogonal_constraints(handles, horizontal):
|
|||||||
yield EqualsConstraint(a=p0.y, b=p1.y)
|
yield EqualsConstraint(a=p0.y, b=p1.y)
|
||||||
|
|
||||||
|
|
||||||
class Line(Item):
|
class Line(Matrices, Updateable):
|
||||||
"""A Line item.
|
"""A Line item.
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
@ -411,6 +416,14 @@ class Line(Item):
|
|||||||
p1, p0 = h1.pos, h0.pos
|
p1, p0 = h1.pos, h0.pos
|
||||||
self._tail_angle = atan2(p1.y - p0.y, p1.x - p0.x) # type: ignore[assignment]
|
self._tail_angle = atan2(p1.y - p0.y, p1.x - p0.x) # type: ignore[assignment]
|
||||||
|
|
||||||
|
def handles(self) -> Sequence[Handle]:
|
||||||
|
"""Return a list of handles owned by the item."""
|
||||||
|
return self._handles
|
||||||
|
|
||||||
|
def ports(self) -> Sequence[Port]:
|
||||||
|
"""Return list of ports."""
|
||||||
|
return self._ports
|
||||||
|
|
||||||
def point(self, x, y):
|
def point(self, x, y):
|
||||||
"""
|
"""
|
||||||
>>> a = Line()
|
>>> a = Line()
|
||||||
|
@ -102,7 +102,7 @@ class BoundingBoxPainter:
|
|||||||
):
|
):
|
||||||
self.item_painter = item_painter
|
self.item_painter = item_painter
|
||||||
|
|
||||||
def paint_item(self, item, cairo):
|
def paint_item(self, item: Item, cairo):
|
||||||
cairo = CairoBoundingBoxContext(cairo)
|
cairo = CairoBoundingBoxContext(cairo)
|
||||||
self.item_painter.paint_item(item, cairo)
|
self.item_painter.paint_item(item, cairo)
|
||||||
# Bounding box is in view (cairo root) coordinates
|
# Bounding box is in view (cairo root) coordinates
|
||||||
|
@ -86,7 +86,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, canvas: Optional[Model[Item]] = None):
|
def __init__(self, canvas: Optional[Model] = None):
|
||||||
Gtk.DrawingArea.__init__(self)
|
Gtk.DrawingArea.__init__(self)
|
||||||
|
|
||||||
self._dirty_items: Set[Item] = set()
|
self._dirty_items: Set[Item] = set()
|
||||||
@ -117,7 +117,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable):
|
|||||||
|
|
||||||
self._qtree: Quadtree[Item, Tuple[float, float, float, float]] = Quadtree()
|
self._qtree: Quadtree[Item, Tuple[float, float, float, float]] = Quadtree()
|
||||||
|
|
||||||
self._canvas: Optional[Model[Item]] = None
|
self._canvas: Optional[Model] = None
|
||||||
if canvas:
|
if canvas:
|
||||||
self._set_canvas(canvas)
|
self._set_canvas(canvas)
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable):
|
|||||||
m.invert()
|
m.invert()
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def _set_canvas(self, canvas: Optional[Model[Item]]) -> None:
|
def _set_canvas(self, canvas: Optional[Model]) -> None:
|
||||||
"""
|
"""
|
||||||
Use view.canvas = my_canvas to set the canvas to be rendered
|
Use view.canvas = my_canvas to set the canvas to be rendered
|
||||||
in the view.
|
in the view.
|
||||||
|
@ -1,45 +1,44 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterable, Optional, Sequence, TypeVar
|
from typing import Collection, Iterable, Optional
|
||||||
|
|
||||||
from typing_extensions import Protocol, runtime_checkable
|
from typing_extensions import Protocol, runtime_checkable
|
||||||
|
|
||||||
T = TypeVar("T")
|
from gaphas.item import Item
|
||||||
T_ct = TypeVar("T_ct", contravariant=True)
|
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
@runtime_checkable
|
||||||
class Model(Protocol[T]):
|
class View(Protocol):
|
||||||
def get_all_items(self) -> Iterable[T]:
|
def request_update(
|
||||||
|
self,
|
||||||
|
items: Collection[Item],
|
||||||
|
matrix_only_items: Collection[Item],
|
||||||
|
removed_items: Collection[Item],
|
||||||
|
) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
def get_parent(self, item: T) -> Optional[T]:
|
|
||||||
|
@runtime_checkable
|
||||||
|
class Model(Protocol):
|
||||||
|
def get_all_items(self) -> Iterable[Item]:
|
||||||
...
|
...
|
||||||
|
|
||||||
def get_children(self, item: T) -> Iterable[T]:
|
def get_parent(self, item: Item) -> Optional[Item]:
|
||||||
...
|
...
|
||||||
|
|
||||||
def sort(self, items: Sequence[T]) -> Iterable[T]:
|
def get_children(self, item: Item) -> Iterable[Item]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def sort(self, items: Collection[Item]) -> Iterable[Item]:
|
||||||
...
|
...
|
||||||
|
|
||||||
def update_now(
|
def update_now(
|
||||||
self, dirty_items: Sequence[T], dirty_matrix_items: Sequence[T]
|
self, dirty_items: Collection[Item], dirty_matrix_items: Collection[Item]
|
||||||
) -> None:
|
) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
def register_view(self, view: View[T]) -> None:
|
def register_view(self, view: View) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
def unregister_view(self, view: View[T]) -> None:
|
def unregister_view(self, view: View) -> None:
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
|
||||||
class View(Protocol[T_ct]):
|
|
||||||
def request_update(
|
|
||||||
self,
|
|
||||||
items: Sequence[T_ct],
|
|
||||||
matrix_only_items: Sequence[T_ct],
|
|
||||||
removed_items: Sequence[T_ct],
|
|
||||||
) -> None:
|
|
||||||
...
|
...
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gaphas.aspect import InMotion, Selection
|
from gaphas.aspect import InMotion, Selection
|
||||||
from gaphas.item import Item
|
from gaphas.item import Element
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def item():
|
def item(connections):
|
||||||
return Item()
|
return Element(connections)
|
||||||
|
|
||||||
|
|
||||||
def test_selection_select(canvas, view, item):
|
def test_selection_select(canvas, view, item):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Test segment aspects for items."""
|
"""Test segment aspects for items."""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gaphas.item import Item
|
from gaphas.item import Element
|
||||||
from gaphas.segment import HandleFinder, Line, Segment, SegmentHandleFinder
|
from gaphas.segment import HandleFinder, Line, Segment, SegmentHandleFinder
|
||||||
from gaphas.tool import ConnectHandleTool
|
from gaphas.tool import ConnectHandleTool
|
||||||
|
|
||||||
@ -11,15 +11,13 @@ def tool(view):
|
|||||||
return ConnectHandleTool(view)
|
return ConnectHandleTool(view)
|
||||||
|
|
||||||
|
|
||||||
def test_segment_fails_for_item(canvas):
|
def test_segment_fails_for_element(canvas, connections):
|
||||||
"""Test if Segment aspect can be applied to Item."""
|
item = Element(connections)
|
||||||
item = Item()
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
Segment(canvas, item)
|
Segment(canvas, item)
|
||||||
|
|
||||||
|
|
||||||
def test_segment(canvas, connections):
|
def test_add_segment_to_line(canvas, connections):
|
||||||
"""Test add a new segment to a line."""
|
|
||||||
line = Line(connections)
|
line = Line(connections)
|
||||||
canvas.add(line)
|
canvas.add(line)
|
||||||
segment = Segment(line, canvas)
|
segment = Segment(line, canvas)
|
||||||
@ -32,7 +30,6 @@ def test_segment(canvas, connections):
|
|||||||
|
|
||||||
|
|
||||||
def test_split_single(canvas, line):
|
def test_split_single(canvas, line):
|
||||||
"""Test single line splitting."""
|
|
||||||
# Start with 2 handles & 1 port, after split: expect 3 handles & 2 ports
|
# Start with 2 handles & 1 port, after split: expect 3 handles & 2 ports
|
||||||
assert len(line.handles()) == 2
|
assert len(line.handles()) == 2
|
||||||
assert len(line.ports()) == 1
|
assert len(line.ports()) == 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user