I propose to separate the logic of mapping the inputs of an interface to a specific operation in selected objects. Lets you call them a Sensor object. Without knowing your implementation language, I will have a general character with this, but you should get this idea.
OperationSensor + OnKeyDown + OnKeyPress + OnKeyUp + OnLeftMouseDown + OnLeftMouseUp + OnNodeSelect + OnNodeDeselect + OnDragStart + OnDragStop
Let's say you have a central class that combines all of the various user interface inputs, UiInputManager . It uses language-specific mechanisms to listen for keyboard and mouse input. It also detects basic operations, such as finding that if the mouse is clicked and then moved, it is a logical “drag and drop”.
UiInputManager // event listeners + keyboard_keydownHandler + keyboard_keyupHandler + mouse_leftdownHandler + mouse_rightdownHandler // active sensor list, can be added to or removed from + Sensors
UiInputManager is NOT responsible for knowing which operations trigger these inputs. It simply notifies its sensors in a specific language.
foreach sensor in Sensors sensor.OnDragStarted
or, if the sensors listen for logical events thrown by the UiInputManager
RaiseEvent DragStarted
You now have plumbing to enter the route into subclasses of OperationSensor. Each OperationSensor has logic related to only one operation. If it finds that the work criteria are met, it creates the corresponding Command object and passes it back.
// Ctrl + zooms in, Ctrl - zooms out ZoomSensor : OperationSensor override OnKeyDown { if keyDown.Char = '+' && keyDown.IsCtrlDepressed base.IssueCommand(new ZoomCommand(changeZoomBy:=10) elseif keyDown.Char = '-' && keyDown.IsCtrlDepressed base.IssueCommand(new ZoomCommand(changeZoomBy:=-10) }
I would recommend that command objects pass from sensors to the UiInputManager. Then the manager can transfer them to the command processing subsystem. This gives the manager the opportunity to notify the Sensors of the completion of the operation, allowing them to reset their internal state, if necessary.
Multistage operations can be handled in two different ways. You can either implement internal automata inside SensorOperation, or you have a Step 1 sensor that creates a Step 2 sensor and add it to the list of active sensors, possibly even removing yourself from the list. When "Step 2" is completed, it can re-add the sensor "Step 1" and delete itself.