Skip to content

Commit f042fe6

Browse files
author
Zerline
committed
Flipping dominos example interactivity: old domino full reset before creating the new dominos ;
+ override Button to skip propagation in certain cases.
1 parent a91e0d6 commit f042fe6

File tree

1 file changed

+141
-58
lines changed

1 file changed

+141
-58
lines changed

examples/dominos/flipping_dominos.py

+141-58
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from .flipping_aztecdiamond import *
55
from sage_widget_adapters.graphs.graph_grid_view_adapter import GraphGridViewAdapter
6-
from sage_combinat_widgets.grid_view_widget import GridViewWidget, ButtonCell, BlankButton, styled_button_cell
6+
from sage_combinat_widgets.grid_view_widget import GridViewWidget, ButtonCell, BlankButton, StyledButtonCell
77
from ipywidgets import Layout
88
from traitlets import dlink, HasTraits, Bool, observe, All
99
from contextlib import contextmanager
@@ -22,6 +22,7 @@ def set_cell(self, obj, pos, val, dirty={}):
2222
we prepare a possible flip
2323
or we try to complete the flip if it has been prepared previously
2424
"""
25+
#print("in the adapter for pos =", pos, "val =", val, "dirty =", dirty)
2526
# Find out the relevant matching for 'pos'
2627
d1 = obj.domino_for_position(pos)
2728
if dirty: # if i'm a neighbor, then flip and return a new obj ; else return an error
@@ -34,7 +35,9 @@ def set_cell(self, obj, pos, val, dirty={}):
3435
continue
3536
if d2 in d1.neighbors():
3637
# Do the flip
38+
print("before flipping :", d1, d2)
3739
obj.flip(d1, d2)
40+
print("after flipping :", d1, d2)
3841
return obj
3942
return Exception("Please select a second domino!")
4043
else:
@@ -43,7 +46,7 @@ def set_cell(self, obj, pos, val, dirty={}):
4346

4447
class ddlink(dlink):
4548
"""Double directional link with logic = or/and/none.
46-
Double_source is a tuple of (source, target) tuples
49+
`double_source` is a tuple of (source, traitname) tuples
4750
Usage: b1 = Button(description = 'B1')
4851
b2 = Button(description = 'B2')
4952
b3 = Button(description = 'B3')
@@ -101,6 +104,9 @@ def _busy_updating(self):
101104
def _update(self, change):
102105
#if self.updating or self.target[0].donottrack:
103106
# return
107+
#if self.target[0].donottrack:
108+
# print("on sort ici !!!")
109+
# return
104110
with self._busy_updating():
105111
if self.logic == 'and':
106112
if self.intermediate_value == False: # aucun bouton pressé avant
@@ -137,38 +143,66 @@ def unlink(self):
137143
source[0].unobserve(self._update, names=source[1])
138144
self.double_source, self.target = None, None
139145

146+
147+
class MyStyledButtonCell(StyledButtonCell):
148+
def _handle_msg(self, msg):
149+
r"""
150+
Override needed to prevent propagation
151+
when a domino is pressed, in some cases.
152+
"""
153+
data = msg['content']['data']
154+
if data['method'] != 'update' or not 'state' in data or ('buffer_paths' in data and data['buffer_paths']):
155+
super(FlippinDominosWidget)._handle_msg(msg)
156+
state = data['state']
157+
try:
158+
self.set_state(state)
159+
except:
160+
pass
161+
162+
163+
def my_styled_button_cell(disabled=False, style_name='', addable=False):
164+
class_name = "{}Button".format(style_name.capitalize())
165+
if disabled:
166+
class_name = "Disabled" + class_name
167+
elif addable:
168+
class_name = "Addable" + class_name
169+
return type(class_name, (MyStyledButtonCell,), {'disable': disabled, 'css_class': style_name, 'addable': addable})
170+
171+
140172
class Domino(HasTraits):
141173
r"""Objet non représenté en lui-même, les 2
142174
boutons qu'il contient étant, eux, des widgets"""
143175
value = Bool()
144176

145-
def __init__(self, parent, b1, b2):
177+
def __init__(self, parent, b1, b2, link=True):
146178
"""A domino has a parent widget and is made of 2 buttons"""
179+
b1.link = None
180+
b2.link = None
181+
b1.value = False
182+
b2.value = False
147183
super(Domino, self).__init__()
184+
self.value = False
148185
self.geometry = DominoGeometry(b1.position, b2.position)
149186
self.parent = parent
150187
self.key = None
151188
self.first = b1
152189
self.second = b2
153190
self.buttons = (b1,b2)
154-
b1.link = dlink((b1, 'value'), (b2, 'value'))
155-
b2.link = dlink((b2, 'value'), (b1, 'value'))
156191
self.link = None
157192
self.direction = None
158193
self.orientation = None
159194
self.compute()
160-
self.donottrack = False
195+
if link:
196+
self.set_links()
161197

162198
def __repr__(self):
163199
if self.value:
164200
return repr(self.first) + ' -> ' + repr(self.second) + " PRESSED"
165201
return repr(self.first) + ' -> ' + repr(self.second)
166202

167203
def compute(self, css_classes=['b0', 'b1', 'b2', 'b3', 'b4']):
168-
"""Compute buttons relative positions.
169-
Create double directional link from both buttons"""
204+
"""Compute buttons relative positions."""
170205
self.geometry.compute()
171-
#print(self.geometry.__dict__)
172206
if css_classes:
173207
for cl in css_classes:
174208
self.first.remove_class(cl)
@@ -196,26 +230,26 @@ def compute(self, css_classes=['b0', 'b1', 'b2', 'b3', 'b4']):
196230
elif self.geometry.orientation == -1:
197231
self.first.add_class('bottom')
198232
self.second.add_class('top')
233+
234+
def set_links(self):
235+
"""Create double directional link from both buttons
236+
and for domino."""
237+
self.first.link = dlink((self.first, 'value'), (self.second, 'value'))
238+
self.second.link = dlink((self.second, 'value'), (self.first, 'value'))
199239
self.link = ddlink(((self.first, 'value'), (self.second, 'value')), (self, 'value'), logic='and', set_at_init=False) # Fresh ddlink
200240

201241
def is_pressed(self):
202242
"""Is the domino pressed?"""
203243
return self.value
204244

205-
# def set_value(self, value):
206-
# """Set domino value
207-
# As we have a directional link,
208-
# the domino value will also be set.
209-
# """
210-
# self.link.unlink()
211-
# self.first.value = value
212-
# self.second.value = value
213-
# self.link = ddlink(((self.first, 'value'), (self.second, 'value')), (self, 'value'), logic='and', set_at_init=False) # Fresh ddlink
214-
215245
def reset(self):
216-
"""Full domino reset"""
217-
#self.set_value(False)
246+
"""Full domino unlink"""
218247
self.link.unlink()
248+
self.first.link.unlink()
249+
self.second.link.unlink()
250+
self.value = False
251+
self.first.value = False
252+
self.second.value = False
219253

220254
def flip(self, other):
221255
"""Flip self with some neighboring domino"""
@@ -248,14 +282,15 @@ def __init__(self, g, css_classes=['b0', 'b1', 'b2', 'b3', 'b4']):
248282
self.css_classes = css_classes
249283
super(FlippingDominosWidget, self).__init__(g, adapter = FlippingDominosAdapter(),
250284
cell_layout = smallblyt,
251-
cell_widget_classes=[styled_button_cell(),
252-
styled_button_cell(style_name='b1'),
253-
styled_button_cell(style_name='b2'),
254-
styled_button_cell(style_name='b3'),
255-
styled_button_cell(style_name='b4'),
285+
cell_widget_classes=[my_styled_button_cell(),
286+
my_styled_button_cell(style_name='b1'),
287+
my_styled_button_cell(style_name='b2'),
288+
my_styled_button_cell(style_name='b3'),
289+
my_styled_button_cell(style_name='b4'),
256290
],
257291
cell_widget_class_index=make_cell_widget_class_index(g),
258292
blank_widget_class = BlankButton)
293+
259294
def draw(self):
260295
self.dominos = {}
261296
super(FlippingDominosWidget, self).draw()
@@ -279,8 +314,10 @@ def apply_matching(self, matching):
279314
"""Apply a matching"""
280315
self.dominos = {}
281316
for d in matching:
282-
self.match(self.children[d.first[0]].children[d.first[1]],
283-
self.children[d.second[0]].children[d.second[1]])
317+
self.match(
318+
self.children[d.first[0]].children[d.first[1]],
319+
self.children[d.second[0]].children[d.second[1]]
320+
)
284321

285322
def update(self):
286323
self.apply_matching(self.value.matching)
@@ -292,54 +329,100 @@ def domino_for_position(self, pos):
292329
geometry = self.value.domino_for_position(pos)
293330
for t in (geometry.first, geometry.second):
294331
if t in self.dominos:
295-
print("domino for position", t, ":", self.dominos[t].geometry, self.dominos[t].value)
296332
return self.dominos[t]
297333

298-
def not_tracking(self, value):
299-
self.donottrack = value
300-
for d in self.dominos.values():
301-
d.donottrack = value
302-
303334
@observe(All)
304335
def set_cell(self, change):
305336
if self.donottrack:
306337
return
307338
if change.name.startswith('cell_'):
308339
print("set_cell()", change.name, change.old, change.new)
309-
else:
310-
print("set_cell()", change.name)
311-
# Try to reset everything right now to avoid unwanted propagations
312-
domino = self.domino_for_position(extract_coordinates(change.name))
313-
# First, we want to make the pressed domino visible to the user
314-
self.not_tracking(True)
315-
domino.first.value = True
316-
domino.second.value = True
317-
# Any pressed neighbor?
340+
click_pos = extract_coordinates(change.name)
341+
domino = self.domino_for_position(click_pos)
342+
if not domino: # or domino.donottrack:
343+
return
344+
# The domino must be entirely pressed
345+
if not domino.first.value or not domino.second.value:
346+
return
347+
# The domino must have a pressed neighbor
318348
other = None
319349
if self.dirty:
320350
for pos in self.dirty:
351+
if other and other.geometry!=self.domino_for_position(pos).geometry:
352+
raise Exception("on a un double dans les voisins pressés: %s et %s" % (
353+
other.geometry, self.domino_for_position(pos).geometry))
321354
other = self.domino_for_position(pos)
322355
if other and not other.geometry in domino.geometry.neighbors():
323356
other = None
324357
continue # we don't have to reset everything, I guess(hope)
325358
if not other:
359+
# Feed the 'dirty' dict and return
326360
self.dirty[domino.geometry.first] = True
327361
self.dirty[domino.geometry.second] = True
328-
self.not_tracking(False)
329362
return
330-
if domino.link:
331-
domino.link.unlink()
332-
if other.link:
333-
other.link.unlink()
334-
self.not_tracking(False)
363+
# Do the flip
335364
super(FlippingDominosWidget, self).set_cell(change)
336-
self.not_tracking(True)
337-
# And now, we want to reset everything before the flip
338-
domino.first.value = False
339-
domino.second.value = False
340-
other.first.value = False
341-
other.second.value = False
342-
# Now, recreate the 2 dominos and compute style
365+
# Unlink and reset values
366+
self.donottrack = True
367+
domino.reset()
368+
other.reset()
369+
# Build our new dominos
370+
new_domino, new_other = None, None
371+
for g1 in self.value.matching:
372+
if g1.first == domino.geometry.first or g1.first == other.geometry.first:
373+
d1, d2 = domino.geometry, other.geometry
374+
if g1.first == domino.geometry.first:
375+
new_domino = Domino(
376+
self,
377+
self.children[g1.first[0]].children[g1.first[1]],
378+
self.children[g1.second[0]].children[g1.second[1]],
379+
link = False
380+
)
381+
self.dominos[domino.key] = new_domino
382+
for g2 in g1.neighbors():
383+
if not g2 in self.value.matching:
384+
continue
385+
if (other.key in (g2.first, g2.second)) or \
386+
(other.key == g1.second and domino.geometry.second in (g2.first, g2.second)):
387+
new_other = Domino(
388+
self,
389+
self.children[g2.first[0]].children[g2.first[1]],
390+
self.children[g2.second[0]].children[g2.second[1]],
391+
link = False
392+
)
393+
self.dominos[other.key] = new_other
394+
break
395+
elif g1.first == other.geometry.first:
396+
new_other = Domino(
397+
self,
398+
self.children[g1.first[0]].children[g1.first[1]],
399+
self.children[g1.second[0]].children[g1.second[1]],
400+
link = False
401+
)
402+
self.dominos[other.key] = new_other
403+
for g2 in g1.neighbors():
404+
if not g2 in self.value.matching:
405+
continue
406+
if (domino.key in (g2.first, g2.second)) or \
407+
(domino.key == g1.second and other.geometry.second in (g2.first, g2.second)):
408+
new_domino = Domino(
409+
self,
410+
self.children[g2.first[0]].children[g2.first[1]],
411+
self.children[g2.second[0]].children[g2.second[1]],
412+
link = False
413+
)
414+
self.dominos[domino.key] = new_domino
415+
break
416+
if new_domino and new_other:
417+
break
418+
# Check that new dominos are sound and the flip has actually been performed
419+
assert(new_domino is not None and new_other is not None)
420+
assert(new_domino.geometry != domino.geometry and new_other.geometry != other.geometry)
421+
# Compute the dominos
422+
new_domino.compute()
423+
new_other.compute()
424+
new_domino.set_links()
425+
new_other.set_links()
426+
# Reset
343427
self.reset_dirty()
344-
self.update()
345-
self.not_tracking(False)
428+
self.donottrack = False

0 commit comments

Comments
 (0)