2828from mhfu_studio .monster .panels import rows , triggers
2929from mhfu_studio .monster .panels .common import kind
3030from mhfu_studio .monster .panels .node_graph import GraphView , LinkSpec , NodeSpec
31- from mhfu_studio .monster .panels .properties import Detail , Plays , PropertyBar
31+ from mhfu_studio .monster .panels .properties import Detail , Play , Plays , PropertyBar
3232from mhfu_studio .monster .panels .widgets import NoScene
3333from mhfu_studio .shell .overlay import Ink
3434from mhfu_studio .ui import kit , theme
5454 " ports in All"
5555)
5656SCOPED = "Wire by dragging ports in All"
57+ UNPLACED = "A rule with no move yet is checked by nothing, so it has no place to move"
5758
5859
5960class BehaviourPanel (kit .Panel ):
@@ -78,6 +79,7 @@ def __init__(self, ws: MonsterWorkspace, studio: Studio) -> None:
7879 #: the rows now drawn (None: All), and what to pick once the next edit is drawn
7980 self ._plan : rows .Plan | None = None
8081 self ._after : tuple [str , str | None ] | None = None
82+ self ._frame_after : str | None = None
8183 self ._scope : str | None = None
8284 self ._outlined : object = None
8385 self ._palette = ALL
@@ -96,7 +98,6 @@ def __init__(self, ws: MonsterWorkspace, studio: Studio) -> None:
9698 tip = "Adds a rule for this trigger. It is checked after every other rule: drag its"
9799 " frame up to raise it" ,
98100 on = self ._add_rule ,
99- icon = "ph.plus" ,
100101 )
101102 self .spread = kit .button (
102103 "Spread out" ,
@@ -116,10 +117,12 @@ def __init__(self, ws: MonsterWorkspace, studio: Studio) -> None:
116117 v .picked .connect (self ._picked )
117118 v .settled .connect (self ._settle )
118119 v .menu_requested .connect (self ._menu )
120+ v .frame_moved .connect (self ._frame_moved )
119121 self .bar = PropertyBar ()
120122 self .bar .param_changed .connect (self ._param )
121123 self .bar .label_changed .connect (self ._label )
122124 self .bar .plays_changed .connect (self ._plays )
125+ self .bar .play_requested .connect (self ._play )
123126
124127 self .outline = triggers .Outline ()
125128 self .outline .picked .connect (self ._scoped )
@@ -177,6 +180,16 @@ def _plays(self, block: str, old: str | None, new: str | None) -> None:
177180 self ._after = (block , new )
178181 self ._settle ()
179182
183+ def _play (self , move : str ) -> None :
184+ """Play in game, as the Moves panel does it, for `move`."""
185+
186+ def play () -> None :
187+ if self .ws .move != move :
188+ self .ws .select_move (move )
189+ self .ws .play_move_in_game ()
190+
191+ self .studio .act ("play move in game" , play )()
192+
180193 def _add_rule (self ) -> None :
181194 t , doc = self ._current (), self .ws .doc
182195 if t is None or doc is None :
@@ -196,6 +209,9 @@ def _menu(self, node: str, menu: QMenu) -> None:
196209 row = plan .row_of (node ) if plan is not None else None
197210 menu .setToolTipsVisible (True )
198211 if m is None or row is None :
212+ move = graph .move_name (node )
213+ if move is not None and plan is not None :
214+ self ._play_entry (menu , move )
199215 return
200216 ref = rows .ref_of (node )
201217 if ref is not None : # a reference or the placeholder: before the play
@@ -205,6 +221,7 @@ def _menu(self, node: str, menu: QMenu) -> None:
205221 menu .addAction ("Delete the play" ).triggered .connect (
206222 lambda : self ._drop_play (block , move )
207223 )
224+ self ._play_entry (menu , move )
208225 return
209226 shown = [n .id for n in row .nodes ]
210227 k , block = shown .index (node ), row .path .blocks [shown .index (node )]
@@ -227,6 +244,11 @@ def _insert_menu(
227244 act .setToolTip (tip )
228245 act .triggered .connect (lambda _ = False , k = kind_ : self ._insert (after , k , to , play ))
229246
247+ def _play_entry (self , menu : QMenu , move : str ) -> None :
248+ act = menu .addAction ("Play in game" )
249+ act .setToolTip ("Plays this move in the running game, as Play in game does in Moves" )
250+ act .triggered .connect (lambda : self ._play (move ))
251+
230252 def _insert (self , after : str , kind_ : str , to : str | None , play : str | None ) -> None :
231253 doc = self .ws .doc
232254 if doc is None :
@@ -255,11 +277,7 @@ def _scope_all(self) -> bool:
255277
256278 def _wire (self , edit : Callable [..., str ], * wire : str ) -> Callable [[graph .Edits ], str ]:
257279 """The edit of a wire intent, or the refusal a trigger's rows give for it."""
258- return (lambda e : edit (e , * wire )) if self ._scope_all () else self ._not_here
259-
260- @staticmethod
261- def _not_here (_ : graph .Edits ) -> str :
262- raise ManifestError (SCOPED )
280+ return (lambda e : edit (e , * wire )) if self ._scope_all () else self ._refuse (SCOPED )
263281
264282 def _delete (self , ids : list [str ]) -> None :
265283 plan = self ._plan
@@ -288,6 +306,31 @@ def _delete(self, ids: list[str]) -> None:
288306 else :
289307 self ._collect (partial (graph .drop_block , block = rows .node_of (i )))
290308
309+ def _frame_moved (self , frame : str , x : float , y : float ) -> None :
310+ """A rule's frame dropped: the rule takes the place among its trigger's rules that the
311+ drop is at, or the drop is refused and the frame goes back."""
312+ plan = self ._plan
313+ row = next ((r for r in plan .rows if r .id == frame ), None ) if plan is not None else None
314+ if plan is None or row is None :
315+ return
316+ if row .rank is None :
317+ self ._collect (self ._refuse (UNPLACED ))
318+ return
319+ ranked = [r for r in plan .rows if r .rank is not None ]
320+ rects = self .view .frame_rects ()
321+ middle = rects [frame ].center ().y ()
322+ to = sum (1 for r in ranked if r is not row and rects [r .id ].center ().y () < middle )
323+ paths = [r .path for r in ranked ]
324+ self ._frame_after = f"{ rows .FRAME } { ranked [to ].rank } " # the places are handed round
325+ self ._collect (lambda e : graph .move_rule (e , paths , ranked .index (row ), to ))
326+
327+ @staticmethod
328+ def _refuse (why : str ) -> Callable [[graph .Edits ], str ]:
329+ def refuse (_ : graph .Edits ) -> str :
330+ raise ManifestError (why )
331+
332+ return refuse
333+
291334 def _delete_now (self , ids : list [str ]) -> None :
292335 self ._delete (ids )
293336 self ._settle ()
@@ -301,7 +344,7 @@ def _create(self, kind_: str, x: float, y: float) -> None:
301344 if doc is None :
302345 return
303346 if not self ._scope_all ():
304- self ._collect (self ._not_here )
347+ self ._collect (self ._refuse ( SCOPED ) )
305348 return
306349 self ._made = model .new_id (doc .manifest .behaviour )
307350 self ._collect (lambda e : graph .add_block (e , kind_ , (x , y )))
@@ -311,14 +354,15 @@ def _settle(self) -> None:
311354 doc , gesture , dropped = self .ws .doc , self ._gesture , self ._dropped
312355 made , self ._gesture , self ._dropped , self ._made = self ._made , None , {}, None
313356 after , self ._after = self ._after , None
357+ frame , self ._frame_after = self ._frame_after , None
314358 if doc is None :
315359 return
316360 if dropped :
317361 gesture = gesture or graph .Gesture (doc )
318362 gesture .run (lambda e : graph .move_nodes (e , dropped ))
319363 if gesture is None :
320364 return
321- done = gesture
365+ done , was = gesture , doc . manifest
322366
323367 def edit () -> None :
324368 self .ws .edit ("" , done .commit )
@@ -329,6 +373,9 @@ def edit() -> None:
329373 if made is not None and m is not None and made in m .behaviour .blocks :
330374 self .view .select ([made ])
331375 self ._show_bar ()
376+ elif frame is not None and m is not was and frame in self .view .frame_rects ():
377+ self .view .select ([frame ])
378+ self ._show_bar ()
332379 elif after is not None and self ._plan is not None :
333380 ends = [n .id for r in self ._plan .rows for n in r .nodes if rows .ref_of (n .id ) == after ]
334381 if ends :
@@ -418,7 +465,7 @@ def _detail(self, m: Manifest, shown: str) -> Detail | None:
418465 mv = m .moves .get (name )
419466 if mv is None :
420467 return None
421- return Detail (node , name , self ._move_tip (mv ), label = mv .label )
468+ return Detail (node , name , self ._move_tip (mv ), label = mv .label , play = Play ( name ) )
422469 blk = m .behaviour .blocks .get (node )
423470 if blk is None or self ._read is None :
424471 return None
@@ -452,6 +499,7 @@ def _ref_detail(self, m: Manifest, shown: str, block: str, move: str | None) ->
452499 label = mv .label ,
453500 note_tip = "A note on this move, kept in the port manifest" ,
454501 plays = Plays (block , move , tuple (m .moves )),
502+ play = Play (move ),
455503 )
456504
457505 @staticmethod
0 commit comments