Move port selection into ConnectionSink
This commit is contained in:
parent
ff5ea4ffc3
commit
4e56050358
@ -4,5 +4,5 @@ Aspects form intermediate items between tools and items.
|
||||
"""
|
||||
|
||||
from gaphas.aspect.connector import ConnectionSink, Connector
|
||||
from gaphas.aspect.handlemove import HandleMove
|
||||
from gaphas.aspect.handlemove import HandleMove, item_at_point
|
||||
from gaphas.aspect.move import Move
|
||||
|
@ -1,18 +1,28 @@
|
||||
from functools import singledispatch
|
||||
from typing import Callable, Optional
|
||||
from typing import Callable, Optional, Tuple
|
||||
|
||||
from typing_extensions import Protocol
|
||||
|
||||
from gaphas.connections import Connections
|
||||
from gaphas.connector import Handle, Port
|
||||
from gaphas.item import Item, matrix_i2i
|
||||
from gaphas.solver import Constraint
|
||||
from gaphas.types import Pos, SupportsFloatPos
|
||||
|
||||
|
||||
class ConnectionSinkType(Protocol):
|
||||
item: Item
|
||||
port: Port
|
||||
port: Optional[Port]
|
||||
|
||||
def __init__(self, item: Item, port: Port):
|
||||
def __init__(self, item: Item, distance: float = 10):
|
||||
...
|
||||
|
||||
def glue(
|
||||
self, pos: SupportsFloatPos, secondary_pos: Optional[SupportsFloatPos] = None
|
||||
) -> Tuple[Optional[Pos], float]:
|
||||
...
|
||||
|
||||
def constraint(self, item: Item, handle: Handle) -> Constraint:
|
||||
...
|
||||
|
||||
|
||||
@ -35,9 +45,10 @@ class ItemConnector:
|
||||
item = self.item
|
||||
matrix = matrix_i2i(item, sink.item)
|
||||
pos = matrix.transform_point(*handle.pos)
|
||||
gluepos, dist = sink.port.glue(pos)
|
||||
matrix.invert()
|
||||
handle.pos = matrix.transform_point(*gluepos)
|
||||
gluepos, dist = sink.glue(pos)
|
||||
if gluepos:
|
||||
matrix.invert()
|
||||
handle.pos = matrix.transform_point(*gluepos)
|
||||
|
||||
def connect(self, sink: ConnectionSinkType) -> None:
|
||||
"""Connect the handle to a sink (item, port).
|
||||
@ -74,7 +85,7 @@ class ItemConnector:
|
||||
handle = self.handle
|
||||
item = self.item
|
||||
|
||||
constraint = sink.port.constraint(item, handle, sink.item)
|
||||
constraint = sink.constraint(item, handle)
|
||||
|
||||
self.connections.connect_item(
|
||||
item, handle, sink.item, sink.port, constraint, callback=callback
|
||||
@ -95,9 +106,31 @@ class ItemConnectionSink:
|
||||
connectable item or port.
|
||||
"""
|
||||
|
||||
def __init__(self, item: Item, port: Port):
|
||||
def __init__(self, item: Item, distance: float = 10) -> None:
|
||||
self.item = item
|
||||
self.port = port
|
||||
self.distance = distance
|
||||
self.port: Optional[Port] = None
|
||||
|
||||
def glue(
|
||||
self, pos: SupportsFloatPos, secondary_pos: Optional[SupportsFloatPos] = None
|
||||
) -> Tuple[Optional[Pos], float]:
|
||||
max_dist = self.distance
|
||||
glue_pos = None
|
||||
for p in self.item.ports():
|
||||
if not p.connectable:
|
||||
continue
|
||||
|
||||
g, d = p.glue(pos)
|
||||
|
||||
if d < max_dist:
|
||||
max_dist = d
|
||||
self.port = p
|
||||
glue_pos = g
|
||||
return glue_pos, max_dist if glue_pos else 1e4
|
||||
|
||||
def constraint(self, item: Item, handle: Handle) -> Constraint:
|
||||
assert self.port, "constraint() can only be called after glue()"
|
||||
return self.port.constraint(item, handle, self.item)
|
||||
|
||||
|
||||
ConnectionSink = singledispatch(ItemConnectionSink)
|
||||
|
@ -1,14 +1,17 @@
|
||||
import logging
|
||||
from functools import singledispatch
|
||||
from typing import Optional, Sequence, Tuple, Union
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from gi.repository import Gdk
|
||||
|
||||
from gaphas.aspect.connector import ConnectionSink, ConnectionSinkType, Connector
|
||||
from gaphas.connector import Handle, Port
|
||||
from gaphas.connector import Handle
|
||||
from gaphas.item import Element, Item
|
||||
from gaphas.types import Pos
|
||||
from gaphas.view import GtkView
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ItemHandleMove:
|
||||
"""Move a handle (role is applied to the handle)"""
|
||||
@ -65,17 +68,24 @@ class ItemHandleMove:
|
||||
if not handle.connectable:
|
||||
return None
|
||||
|
||||
connectable, port, glue_pos = port_at_point(
|
||||
view, pos, distance=distance, exclude=(item,)
|
||||
)
|
||||
connectable = item_at_point(view, pos, distance=distance, exclude=(item,))
|
||||
if not connectable:
|
||||
return None
|
||||
|
||||
# check if item and found item can be connected on closest port
|
||||
if connectable and port and glue_pos:
|
||||
sink = ConnectionSink(connectable)
|
||||
|
||||
ix, iy = view.get_matrix_v2i(connectable).transform_point(*pos)
|
||||
iglue_pos, _ = sink.glue((ix, iy))
|
||||
if not iglue_pos:
|
||||
return None
|
||||
|
||||
glue_pos = view.get_matrix_i2v(connectable).transform_point(*iglue_pos)
|
||||
|
||||
if glue_pos:
|
||||
model = self.view.model
|
||||
assert model
|
||||
connections = model.connections
|
||||
connector = Connector(self.item, self.handle, connections)
|
||||
sink = ConnectionSink(connectable, port)
|
||||
|
||||
if connector.allow(sink):
|
||||
# transform coordinates from view space to the item
|
||||
@ -149,58 +159,31 @@ class ElementHandleMove(ItemHandleMove):
|
||||
self.view.get_window().set_cursor(self.cursor)
|
||||
|
||||
|
||||
def port_at_point(
|
||||
# Maybe make this an iterator? so extra checks can be done on the item
|
||||
def item_at_point(
|
||||
view: GtkView,
|
||||
vpos: Pos,
|
||||
distance: float = 10,
|
||||
pos: Pos,
|
||||
distance: float = 0.5,
|
||||
exclude: Sequence[Item] = (),
|
||||
) -> Union[Tuple[Item, Port, Tuple[float, float]], Tuple[None, None, None]]:
|
||||
"""Find item with port closest to specified position.
|
||||
) -> Optional[Item]:
|
||||
"""Return the topmost item located at ``pos`` (x, y).
|
||||
|
||||
List of items to be ignored can be specified with `exclude`
|
||||
parameter.
|
||||
|
||||
Tuple is returned
|
||||
|
||||
- found item
|
||||
- closest, connectable port
|
||||
- closest point on found port (in view coordinates)
|
||||
|
||||
:Parameters:
|
||||
vpos
|
||||
Position specified in view coordinates.
|
||||
distance
|
||||
Max distance from point to a port (default 10)
|
||||
exclude
|
||||
Set of items to ignore.
|
||||
Parameters:
|
||||
- view: a view
|
||||
- pos: Position, a tuple ``(x, y)`` in view coordinates
|
||||
- selected: if False returns first non-selected item
|
||||
"""
|
||||
v2i = view.get_matrix_v2i
|
||||
vx, vy = vpos
|
||||
|
||||
max_dist = distance
|
||||
port = None
|
||||
glue_pos = None
|
||||
item = None
|
||||
|
||||
rect = (vx - distance, vy - distance, distance * 2, distance * 2)
|
||||
for i in reversed(list(view.get_items_in_rectangle(rect))):
|
||||
if i in exclude:
|
||||
item: Item
|
||||
for item in reversed(list(view.get_items_in_rectangle((pos[0], pos[1], 1, 1)))):
|
||||
if item in exclude:
|
||||
continue
|
||||
for p in i.ports():
|
||||
if not p.connectable:
|
||||
continue
|
||||
|
||||
ix, iy = v2i(i).transform_point(vx, vy)
|
||||
pg, d = p.glue((ix, iy))
|
||||
|
||||
if d >= max_dist:
|
||||
continue
|
||||
|
||||
max_dist = d
|
||||
item = i
|
||||
port = p
|
||||
|
||||
# transform coordinates from connectable item space to view space
|
||||
i2v = view.get_matrix_i2v(i).transform_point
|
||||
glue_pos = i2v(*pg)
|
||||
return item, port, glue_pos # type: ignore[return-value]
|
||||
v2i = view.get_matrix_v2i(item)
|
||||
ix, iy = v2i.transform_point(*pos)
|
||||
item_distance = item.point(ix, iy)
|
||||
if item_distance is None:
|
||||
log.warning("Item distance is None for %s", item)
|
||||
continue
|
||||
if item_distance < distance:
|
||||
return item
|
||||
return None
|
||||
|
@ -4,7 +4,7 @@ from typing import Optional, Tuple, Union
|
||||
from gi.repository import Gdk, Gtk
|
||||
from typing_extensions import Protocol
|
||||
|
||||
from gaphas.aspect import HandleMove, Move
|
||||
from gaphas.aspect import HandleMove, Move, item_at_point
|
||||
from gaphas.canvas import ancestors
|
||||
from gaphas.connector import Handle
|
||||
from gaphas.geometry import distance_point_point_fast
|
||||
@ -116,30 +116,6 @@ def on_drag_end(gesture, offset_x, offset_y, drag_state):
|
||||
drag_state.moving = set()
|
||||
|
||||
|
||||
def item_at_point(view: GtkView, pos: Pos, selected: bool = True) -> Optional[Item]:
|
||||
"""Return the topmost item located at ``pos`` (x, y).
|
||||
|
||||
Parameters:
|
||||
- view: a view
|
||||
- pos: Position, a tuple ``(x, y)`` in view coordinates
|
||||
- selected: if False returns first non-selected item
|
||||
"""
|
||||
item: Item
|
||||
for item in reversed(list(view.get_items_in_rectangle((pos[0], pos[1], 1, 1)))):
|
||||
if not selected and item in view.selection.selected_items:
|
||||
continue # skip selected items
|
||||
|
||||
v2i = view.get_matrix_v2i(item)
|
||||
ix, iy = v2i.transform_point(*pos)
|
||||
item_distance = item.point(ix, iy)
|
||||
if item_distance is None:
|
||||
log.warning("Item distance is None for %s", item)
|
||||
continue
|
||||
if item_distance < 0.5:
|
||||
return item
|
||||
return None
|
||||
|
||||
|
||||
def handle_at_point(
|
||||
view: GtkView, pos: Pos, distance: int = 6
|
||||
) -> Union[Tuple[Item, Handle], Tuple[None, None]]:
|
||||
|
@ -177,14 +177,14 @@ def test_remove_connected_item():
|
||||
number_cons2 = len(canvas.solver.constraints)
|
||||
|
||||
conn = Connector(l1, l1.handles()[0], canvas.connections)
|
||||
sink = ConnectionSink(b1, b1.ports()[0])
|
||||
sink = ConnectionSink(b1)
|
||||
|
||||
conn.connect(sink)
|
||||
|
||||
assert canvas.connections.get_connection(l1.handles()[0])
|
||||
|
||||
conn = Connector(l1, l1.handles()[1], canvas.connections)
|
||||
sink = ConnectionSink(b2, b2.ports()[0])
|
||||
sink = ConnectionSink(b2)
|
||||
|
||||
conn.connect(sink)
|
||||
|
||||
|
@ -78,7 +78,7 @@ def test_get_unselected_item_at_point(view, box):
|
||||
view.selection.select_items(box)
|
||||
|
||||
assert item_at_point(view, (10, 10)) is box
|
||||
assert item_at_point(view, (10, 10), selected=False) is None
|
||||
assert item_at_point(view, (10, 10), exclude=(box,)) is None
|
||||
|
||||
|
||||
def test_get_handle_at_point(view, canvas, connections):
|
||||
|
@ -18,11 +18,11 @@ def test_undo_on_delete_element(revert_undo, undo_fixture):
|
||||
|
||||
canvas.add(line)
|
||||
|
||||
sink = ConnectionSink(b1, b1.ports()[0])
|
||||
sink = ConnectionSink(b1)
|
||||
connector = Connector(line, line.handles()[0], canvas.connections)
|
||||
connector.connect(sink)
|
||||
|
||||
sink = ConnectionSink(b2, b2.ports()[0])
|
||||
sink = ConnectionSink(b2)
|
||||
connector = Connector(line, line.handles()[-1], canvas.connections)
|
||||
connector.connect(sink)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user