import time
from collections import deque
# import jpy_canvas
import ipyevents as ipe
from flatland.envs.agent_utils import with_direction
from flatland.utils.editor_interfaces import AbstractController, AbstractModel, AbstractView
[docs]
class Controller(AbstractController):
"""
Controller to handle incoming events from the ipywidgets
Updates the editor/model.
Calls the View directly for things which do not directly effect the model
(this means the mouse drag path before it is interpreted as transitions)
"""
def __init__(self, model:"AbstractModel", view:AbstractView):
self.editor = self.model = model
self.view = view
self.q_events = deque()
self.drawMode = "Draw"
self.bMouseDown = False
self.boundingRectWidth = None
self.boundingRectHeight = None
[docs]
def set_model(self, model):
self.model = model
def _getCoords(self, event:dict):
#x = event['canvasX']
#y = event['canvasY']
x = event['relativeX']
y = event['relativeY']
#self.debug("debug:", x, y)
return x, y
[docs]
def getModKeys(self, event:dict):
bShift = event["shiftKey"]
bCtrl = event["ctrlKey"]
bAlt = event["altKey"]
return bShift, bCtrl, bAlt
#def handle_event(self, event:dict):
[docs]
def handle_event(self, event: ipe.Event):
if "boundingRectWidth" in event:
self.boundingRectWidth = event["boundingRectWidth"]
if "boundingRectHeight" in event:
self.boundingRectHeight = event["boundingRectHeight"]
if event['type'] == 'mousemove':
nButtons = int(event["buttons"])
if nButtons > 0:
self.bMouseDown = True
self.on_mouse_move(event)
else: # nButtons == 0, ie mouse is now up
if self.bMouseDown: # mouse was down, now up
self.bMouseDown = False
self.log("testing 123")
self.log("mouse up event: ", event)
self.log("lrcStroke: ", self.model.lrcStroke)
self.log("testing 456")
self.on_mouse_move(event) # process a move event with no buttons pressed
elif event['type'] == 'click':
nStroke = self.model.get_len_stroke()
if nStroke == 0:
self.on_click(event)
else:
self.log("ignoring click, stroke len:", nStroke)
self.debug(event)
#self.on_click(event)
[docs]
def on_click(self, event):
self.debug("on_click: ", event)
x, y = self._getCoords(event)
rc_cell = self.view.xy_to_rc(x, y)
bShift, bCtrl, bAlt = self.getModKeys(event)
if bCtrl and not bShift and not bAlt: # only ctrl -> click agent
self.model.click_agent(rc_cell)
self.model.clear_stroke()
elif bShift and bCtrl: # ctrl+shift -> add_target
self.model.add_target(rc_cell)
self.model.clear_stroke()
elif bAlt and not bShift and not bCtrl: # only alt -> clear cell
self.model.clear_cell(rc_cell)
self.model.clear_stroke()
self.debug("click in cell", rc_cell)
self.model.debug_cell(rc_cell)
if self.model.selected_agent is not None:
self.model.clear_stroke()
[docs]
def set_debug(self, event):
self.model.set_debug(event["new"])
[docs]
def set_debug_move(self, event):
self.model.set_debug_move(event["new"])
[docs]
def set_draw_mode(self, event):
self.set_draw_mode = event["new"]
[docs]
def set_filename(self, event):
self.model.set_filename(event["new"])
[docs]
def on_mouse_move(self, event: dict):
""" Mouse motion event handler for drawing.
"""
x, y = self._getCoords(event)
q_events = self.q_events
# only log mousedown drag events, unless debug_move is set
nButtons = int(event["buttons"])
#if self.model.debug_bool and (nButtons > 0 or self.model.debug_move_bool):
# in fact we already filter so we only get drags and mouse up.
self.debug_event(event)
# If the mouse is held down, enqueue an event in our own queue
# The intention was to avoid too many redraws.
if nButtons > 0:
q_events.append((time.time(), x, y))
bShift, bCtrl, bAlt = self.getModKeys(event)
# Reset the stroke, if ALT, CTRL or SHIFT pressed
if bShift or bCtrl or bAlt:
self.model.clear_stroke()
while len(q_events) > 0:
t, x, y = q_events.popleft()
return
# NCW: this can't be right. If the mouse is not held down, treat it as a mouseup, and draw the stroke.
#else:
# self.model.clear_stroke()
# JW: I think this clause causes all editing to fail once an agent is selected.
# I also can't see why it's necessary. So I've if-falsed it out.
if False:
if self.model.selected_agent is not None:
self.model.clear_stroke()
while len(q_events) > 0:
t, x, y = q_events.popleft()
return
# Process the low-level events in our queue:
# Draw a black square to indicate a trail
# Convert the xy position to a cell rc
# Enqueue transitions across cells in another queue
if len(q_events) > 0:
t_now = time.time()
if t_now - q_events[0][0] > 0.1: # wait before trying to draw
while len(q_events) > 0:
t, x, y = q_events.popleft() # get events from our queue
self.view.drag_path_element(x, y)
# Translate and scale from x,y to integer row,col (note order change)
rc_cell = self.view.xy_to_rc(x, y)
self.editor.drag_path_element(rc_cell)
self.view.redisplay_image()
#self.view.redraw()
# if mouse up, process the stroke
if nButtons == 0:
self.model.mod_path(not event["shiftKey"])
[docs]
def refresh(self, event):
self.debug("refresh")
self.view.redraw()
[docs]
def clear(self, event):
self.model.clear()
[docs]
def clear_stroke(self, msg:str=""):
self.debug("controller clear_stroke: ", msg)
self.model.clear_stroke()
[docs]
def reset(self, event):
self.log("Reset - nAgents:", self.view.regen_n_agents.value)
self.log("Reset - size:", self.model.regen_size_width)
self.log("Reset - size:", self.model.regen_size_height)
self.model.reset(regenerate_schedule=self.view.replace_agents.value,
nAgents=self.view.regen_n_agents.value)
[docs]
def rotate_agent(self, event):
self.log("Rotate Agent:", self.model.selected_agent)
if self.model.selected_agent is not None:
for agent_idx, agent in enumerate(self.model.env.agents):
if agent is None:
continue
if agent_idx == self.model.selected_agent:
new_direction = (agent.initial_configuration[1] + 1) % 4
agent.initial_configuration = with_direction(agent.initial_configuration, new_direction)
agent.current_configuration = with_direction(agent.current_configuration, new_direction)
agent.old_configuration = with_direction(agent.old_configuration, new_direction)
self.model.redraw()
[docs]
def reset_agents(self, event):
self.log("Restart Agents - nAgents:", self.view.regen_n_agents.value)
self.model.env.reset(False, False)
self.refresh(event)
[docs]
def regenerate(self, event):
method = self.view.regen_method.value
n_agents = self.view.regen_n_agents.value
self.model.regenerate(method, n_agents)
[docs]
def set_regen_width(self, event):
self.model.set_regen_width(event["new"])
[docs]
def set_regen_height(self, event):
self.model.set_regen_height(event["new"])
[docs]
def load(self, event):
self.model.load()
[docs]
def save(self, event):
self.model.save()
[docs]
def save_image(self, event):
self.model.save_image()
[docs]
def step(self, event):
self.model.step()
[docs]
def log(self, *args, **kwargs):
if self.view is None:
print(*args, **kwargs)
else:
self.view.log(*args, **kwargs)
[docs]
def debug(self, *args, **kwargs):
self.model.debug(*args, **kwargs)
[docs]
def debug_event(self, event):
self.model.debug_event(event)
[docs]
def getBoundingRectYX(self):
return self.boundingRectHeight, self.boundingRectWidth