Home Reference Source Test Repository

src/js/util/mouse-handler.js

import Node from '../data/node/node';
import Edge from '../data/edge/edge';
import Label from '../data/label';
import ui from '../ui/ui';

import PanTool from '../tool/pan-tool';

/**
 * Mouse event handler. Calls the appropriate event handler for the current tool based on user actions.
 * @class MouseHandler
 */
class MouseHandler {

  // distance the mouse needs to move to start a drag
  // this prevents accidentally dragging a few pixels when actually trying to click
  DRAG_THRESHOLD = 7;

  selectedObject = null;
  draggedObject = null;

  clickStartX = null;
  clickStartY = null;
  mousePressed = false;
  isDragging = false;

  rightClickStartX = null;
  rightClickStartY = null;
  rightMousePressed = false;
  isRightDragging = false;
  wasRightDragging = false;

  panTool = new PanTool();

  /**
   * Constructs a MouseHandler instance.
   * @param  {Graph} graph - The current Graph object.
   * @constructs MouseHandler
   */
  constructor(graph) {
    this.graph = graph;
  }

  /**
   * Update the graph reference.
   * @param {Graph} newGraph - The new Graph object.
   */
  resetGraph(newGraph) {
    this.graph = newGraph;
    this.selectedObject = null;
    this.draggedObject = null;

    this.clickStartX = null;
    this.clickStartY = null;
    this.mousePressed = false;
    this.isDragging = false;
  }

  /**
   * Handles the mousedown event for the left mouse button.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {Tool} currentTool - The currently selected tool.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   */
  downListener(event, currentTool, x, y) {
    this.mousePressed = true;
    this.clickStartX = x;
    this.clickStartY = y;

    if (this.graph.hasComponent(x, y)) {
      let component = this.graph.getComponent(x, y);
      if (currentTool.preSelectObject(event, this.graph, component, x, y)) {
        // console.log('Preselect Object Ok!');
        this.selectedObject = component;
      } else {
        // console.log('Preselect Object Denied!');
        this.selectedObject = null;
      }
    } else {
      // console.log('Preselect None!');
      currentTool.preSelectNone(event, this.graph, x, y);
      this.selectedObject = null;
    }
  }

  /**
   * Handles the mouseup event for the left mouse button.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {Tool} currentTool - The currently selected tool.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   */
  upListener(event, currentTool, x, y) {
    // check if dragging
    if (this.isDragging) {
      this.isDragging = false;

      // drop object
      if (this.graph.hasComponent(x, y, this.draggedObject)) {
        currentTool.dropOnObject(event, this.graph, this.draggedObject, this.graph.getComponent(x, y), x, y);
      } else {
        currentTool.dropOnNone(event, this.graph, this.draggedObject, x, y);
      }
      this.draggedObject = null;
    } else if (this.mousePressed) {
      // click
      let component = null;
      if (this.graph.hasComponent(x, y)) {
        component = this.graph.getComponent(x, y);
      }
      if (component === this.selectedObject) {
        if (component) {
          currentTool.selectObject(event, this.graph, component, x, y);
          ui.sidebar.updateSidebar(component);
        } else {
          currentTool.selectNone(event, this.graph, x, y);
          ui.sidebar.updateSidebar();
        }
      } else {
        currentTool.abortSelect(this.graph, x, y);
      }
    }
    this.mousePressed = false;
    this.selectedObject = null;
  }

  /**
   * Handles the mousemove event for the left mouse button.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {Tool} currentTool - The currently selected tool.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   */
  moveListener(event, currentTool, x, y) {
    ui.statusBar.updateMouse(x, y);

    if (!this.isDragging) {
      // check for dragging
      if (this.mousePressed) {
        // check if mouse movement passes threshold
        let dx = x - this.clickStartX;
        let dy = y - this.clickStartY;

        if (Math.sqrt(dx * dx + dy * dy) >= this.DRAG_THRESHOLD) {
          // console.log('Drag Threshold!');
          this.isDragging = true;
          if (this.selectedObject !== null) {
            if (currentTool.preDragObject(event, this.graph, this.selectedObject, x, y)) {
              this.draggedObject = this.selectedObject;
            } else {
              this.selectedObject = null;
              this.draggedObject = null;
            }
          } else {
            currentTool.preDragNone(event, this.graph, x, y);
            this.draggedObject = this.selectedObject;
          }
        }
      } else {
        // regular move
        // hover effect?
      }
    } else if (this.draggedObject) {
      // handle dragging object
      currentTool.dragObject(event, this.graph, this.draggedObject, x, y);
      ui.sidebar.updateSidebar(this.draggedObject);
    } else if (this.graph.hasComponent(x, y)) {
      // handle dragging over object
      currentTool.dragOverObject(event, this.graph, this.graph.getComponent(x, y), x, y);
      ui.sidebar.updateSidebar();
    } else {
      // handle dragging empty space
      currentTool.dragNone(event, this.graph, x, y);
    }
  }

  /**
   * Handles the mousedown event for the right mouse button.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   */
  rightDownListener(event, x, y) {
    this.rightMousePressed = true;
    this.rightClickStartX = x;
    this.rightClickStartY = y;

    this.panTool.preSelectNone(event, this.graph, x, y);
  }

  /**
   * Handles the mouseup event for the right mouse button.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   */
  rightUpListener(event, x, y) {
    // check if dragging
    if (this.isRightDragging) {
      this.isRightDragging = false;
      this.panTool.dropOnNone(event, this.graph, null, x, y);
    } else if (this.rightMousePressed) {
      // click
      this.panTool.selectNone(event, this.graph, x, y);
    }
    this.rightMousePressed = false;
  }

  /**
   * Handles the mousemove event for the right mouse button.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   */
  rightMoveListener(event, x, y) {
    ui.statusBar.updateMouse(x, y);
    if (!this.isRightDragging) {
      // check for dragging
      if (this.rightMousePressed) {
        // check if mouse movement passes threshold
        let dx = x - this.rightClickStartX;
        let dy = y - this.rightClickStartY;

        if (Math.sqrt(dx * dx + dy * dy) >= this.DRAG_THRESHOLD) {
          this.isRightDragging = true;
          this.wasRightDragging = true;
          this.panTool.preDragNone(this.graph, x, y);
        }
      }
    } else {
      // handle dragging
      this.panTool.dragNone(event, this.graph, x, y);
    }
  }

  /**
   * Handles the contextmenu event.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   * @param  {ContextMenu} contextMenu - ContextMenu object.
   */
  contextmenuEventListener(event, x, y, contextMenu) {
    if (this.wasRightDragging) {
      // prevent default context menu
      event.preventDefault();
      this.wasRightDragging = false;
    } else if (event.target === document.getElementById('canvas')) {
      // open context menu if mouse was not right dragging
      contextMenu.contextmenuEventListener(event, x, y);
    }
  }

  /**
   * Get the graph component that is under the mouse when the context menu event was triggered.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   * @return {(Node|Edge|Label)} - The object that is under the mouse.
   */
  contextComponent(event, x, y) {
    if (this.graph.hasComponent(x, y)) {
      this.selectedObject = this.graph.getComponent(x, y);
    }
    return this.selectedObject;
  }

  /**
   * Handle the context menu "add" action.
   * @param  {string} arg - Name of object to add.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   */
  contextAdd(arg, x, y) {
    if (arg === 'edge') {
      ui.toolbar.selectToolByName('edge');
      ui.toolbar.toolMap.edge.selectNode(this.graph, this.selectedObject);
    } else {
      ui.toolbar.selectToolByName('node');
      ui.toolbar.toolMap.node.addNode(arg, this.graph, x, y);
    }
  }

  /**
   * Handle the context menu "toggle" action.
   * @param  {string} arg - Name of the field to toggle.
   */
  contextToggle(arg) {
    this.selectedObject[arg] = !this.selectedObject[arg];
  }

  /**
   * Handle the context menu "delete" action.
   */
  contextDelete() {
    if (this.selectedObject instanceof Node) {
      this.graph.removeNode(this.selectedObject);
    } else if (this.selectedObject instanceof Edge) {
      this.graph.removeEdge(this.selectedObject);
    } else if (this.selectedObject instanceof Label) {
      this.selectedObject.content = '';
    }
  }

  /**
   * Handle context menu object selection.
   * @param  {Event} event - The Event object from the event listener.
   * @param  {Tool} currentTool - The currently selected tool.
   * @param  {number} x - Mouse x-coordinate (in canvas coordinates).
   * @param  {number} y - Mouse y-coordinate (in canvas coordinates).
   */
  contextSelect(event, currentTool, x, y) {
    currentTool.selectObject(event, this.graph, this.selectedObject, x, y);
  }
}

export { MouseHandler };
export default MouseHandler;