Skip to content

Commit

Permalink
Position can emit state changes
Browse files Browse the repository at this point in the history
  • Loading branch information
amolenaar committed Jan 3, 2021
1 parent 095007b commit bcd2a57
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 16 deletions.
38 changes: 35 additions & 3 deletions gaphas/position.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import annotations

from typing import SupportsFloat, Tuple, Union
from typing import Callable, Set, SupportsFloat, Tuple, Union

from gaphas.matrix import Matrix
from gaphas.solver import NORMAL, BaseConstraint, Variable
from gaphas.types import SupportsFloatPos, TypedProperty
from gaphas.types import Pos, SupportsFloatPos, TypedProperty


class Position:
Expand All @@ -22,6 +22,32 @@ class Position:
def __init__(self, x, y, strength=NORMAL):
self._x = Variable(x, strength)
self._y = Variable(y, strength)
self._handlers: Set[Callable[[Position, Pos], None]] = set()
self._setting_pos = 0

def add_handler(self, handler: Callable[[Position, Pos], None]) -> None:
if not self._handlers:
self._x.add_handler(self._propagate_x)
self._y.add_handler(self._propagate_y)
self._handlers.add(handler)

def remove_handler(self, handler: Callable[[Position, Pos], None]) -> None:
self._handlers.discard(handler)
if not self._handlers:
self._x.remove_handler(self._propagate_x)
self._y.remove_handler(self._propagate_y)

def notify(self, oldpos: Pos) -> None:
for handler in self._handlers:
handler(self, oldpos)

def _propagate_x(self, variable, oldval):
if not self._setting_pos:
self.notify((oldval, self._y.value))

def _propagate_y(self, variable, oldval):
if not self._setting_pos:
self.notify((self._x.value, oldval))

@property
def strength(self) -> int:
Expand All @@ -42,7 +68,13 @@ def _set_y(self, v: SupportsFloat) -> None:

def _set_pos(self, pos: Union[Position, SupportsFloatPos]) -> None:
"""Set handle position (Item coordinates)."""
self._x.value, self._y.value = pos
oldpos = (self._x.value, self._y.value)
self._setting_pos += 1
try:
self._x.value, self._y.value = pos
finally:
self._setting_pos -= 1
self.notify(oldpos)

pos: TypedProperty[Tuple[Variable, Variable], Union[Position, SupportsFloatPos]]
pos = property(lambda s: (s._x, s._y), _set_pos, doc="The position.")
Expand Down
11 changes: 11 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,14 @@ def revert_undo(undo_fixture):
yield
state.observers.remove(state.revert_handler)
state.subscribers.remove(undo_fixture[1])


@pytest.fixture
def handler():
events = []

def handler(*args):
events.append(args)

handler.events = events # type: ignore[attr-defined]
return handler
33 changes: 33 additions & 0 deletions tests/test_position.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,39 @@ def test_position_can_compare_with_tuple():
assert pos < (4, 4)


def test_position_notifies_on_x_change(handler):
pos = Position(3, 3)
pos.add_handler(handler)

pos.x = 4

assert handler.events
assert handler.events[0][0] is pos
assert handler.events[0][1] == (3.0, 3.0)


def test_position_notifies_on_y_change(handler):
pos = Position(3, 3)
pos.add_handler(handler)

pos.y = 4

assert handler.events
assert handler.events[0][0] is pos
assert handler.events[0][1] == (3.0, 3.0)


def test_position_notifies_on_pos_change(handler):
pos = Position(3, 3)
pos.add_handler(handler)

pos.pos = (4, 4)

assert len(handler.events) == 1
assert handler.events[0][0] is pos
assert handler.events[0][1] == (3.0, 3.0)


def test_matrix_projection_exposes_variables():
proj = MatrixProjection(Position(0, 0), Matrix())

Expand Down
15 changes: 2 additions & 13 deletions tests/test_solver_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,6 @@
from gaphas.solver.constraint import Constraint, ContainsConstraints


@pytest.fixture
def handler():
events = []

def handler(e):
events.append(e)

handler.events = events # type: ignore[attr-defined]
return handler


def test_base_constraint_implements_constraint_protocol():
c = BaseConstraint(Variable())

Expand All @@ -35,7 +24,7 @@ def test_constraint_propagates_variable_changed(handler):

v.value = 3

assert handler.events == [c]
assert handler.events == [(c,)]


def test_multi_constraint(handler):
Expand All @@ -46,7 +35,7 @@ def test_multi_constraint(handler):

v.value = 3

assert handler.events == [c]
assert handler.events == [(c,)]


def test_default_constraint_can_not_solve():
Expand Down

0 comments on commit bcd2a57

Please sign in to comment.