NGL@1.0.0-beta.7 Home Manual Reference Source Gallery

src/controls/mouse-controls.js

/**
 * @file Mouse Controls
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @private
 */

import { MouseActionPresets } from './mouse-actions.js'

/**
 * Strings to describe mouse events (including optional keyboard modifiers).
 * Must contain an event type: "scroll", "drag", "click", "doubleClick",
 * "hover", "clickPick" or "hoverPick". Optionally contain one or more
 * (seperated by plus signs) keyboard modifiers: "alt", "ctrl", "meta" or
 * "shift". Can contain the mouse button performing the event: "left",
 * "middle" or "right". The type, key and button parts must be seperated by
 * dashes.
 *
 * @example
 * // triggered on scroll event (no key or button)
 * "scroll"
 *
 * @example
 * // triggered on scroll event while shift key is pressed
 * "scroll-shift"
 *
 * @example
 * // triggered on drag event with left mouse button
 * "drag-left"
 *
 * @example
 * // triggered on drag event with right mouse button
 * // while ctrl and shift keys are pressed
 * "drag-right-ctrl+shift"
 *
 * @typedef {String} TriggerString
 */

/**
 * Get event type, key and button
 * @param  {TriggerString} str - input trigger string
 * @return {Array} event type, key and button
 */
function triggerFromString (str) {
  const tokens = str.split(/[-+]/)

  let type = ''
  if (tokens.includes('scroll')) type = 'scroll'
  if (tokens.includes('drag')) type = 'drag'
  if (tokens.includes('click')) type = 'click'
  if (tokens.includes('doubleClick')) type = 'doubleClick'
  if (tokens.includes('hover')) type = 'hover'
  if (tokens.includes('clickPick')) type = 'clickPick'
  if (tokens.includes('hoverPick')) type = 'hoverPick'

  let key = 0
  if (tokens.includes('alt')) key += 1
  if (tokens.includes('ctrl')) key += 2
  if (tokens.includes('meta')) key += 4
  if (tokens.includes('shift')) key += 8

  let button = 0
  if (tokens.includes('left')) button += 1
  if (tokens.includes('right')) button += 2
  if (tokens.includes('middle')) button += 4

  return [ type, key, button ]
}

/**
 * Mouse controls
 */
class MouseControls {
  /**
   * @param {Stage} stage - the stage object
   * @param {Object} [params] - the parameters
   * @param {String} params.preset - one of "default", "pymol", "coot"
   * @param {String} params.disabled - flag to disable all actions
   */
  constructor (stage, params) {
    const p = params || {}

    this.stage = stage
    this.mouse = stage.mouseObserver
    this.actionList = []

    /**
     * Flag to disable all actions
     * @type {Boolean}
     */
    this.disabled = p.disabled || false

    this.preset(p.preset || 'default')
  }

  run (type, ...args) {
    if (this.disabled) return

    const key = this.mouse.key || 0
    const button = this.mouse.buttons || 0

    this.actionList.forEach(a => {
      if (a.type === type && a.key === key && a.button === button) {
        a.callback(this.stage, ...args)
      }
    })
  }

  /**
   * Add a new mouse action triggered by an event, key and button combination.
   * The {@link MouseActions} class provides a number of static methods for
   * use as callback functions.
   *
   * @example
   * // change ambient light intensity on mouse scroll
   * // while the ctrl and shift keys are pressed
   * stage.mouseControls.add( "scroll-ctrl+shift", function( stage, delta ){
   *     var ai = stage.getParameters().ambientIntensity;
   *     stage.setParameters( { ambientIntensity: Math.max( 0, ai + delta / 50 ) } );
   * } );
   *
   * @example
   * // Call the MouseActions.zoomDrag method on mouse drag events
   * // with left and right mouse buttons simultaneous
   * stage.mouseControls.add( "drag-left+right", MouseActions.zoomDrag );
   *
   * @param {TriggerString} triggerStr - the trigger for the action
   * @param {function(stage: Stage, ...args: Any)} callback - the callback function for the action
   * @return {undefined}
   */
  add (triggerStr, callback) {
    const [ type, key, button ] = triggerFromString(triggerStr)

    this.actionList.push({ type, key, button, callback })
  }

  /**
   * Remove a mouse action. The trigger string can contain an asterix (*)
   * as a wildcard for any key or mouse button. When the callback function
   * is given, only actions that call that function are removed.
   *
   * @example
   * // remove actions triggered solely by a scroll event
   * stage.mouseControls.remove( "scroll" );
   *
   * @example
   * // remove actions triggered by a scroll event, including
   * // those requiring a key pressed or mouse button used
   * stage.mouseControls.remove( "scroll-*" );
   *
   * @example
   * // remove actions triggered by a scroll event
   * // while the shift key is pressed
   * stage.mouseControls.remove( "scroll-shift" );
   *
   * @param {TriggerString} triggerStr - the trigger for the action
   * @param {Function} [callback] - the callback function for the action
   * @return {undefined}
   */
  remove (triggerStr, callback) {
    const wildcard = triggerStr.includes('*')
    const [ type, key, button ] = triggerFromString(triggerStr)

    const actionList = this.actionList.filter(function (a) {
      return !(
        (a.type === type || (wildcard && type === '')) &&
        (a.key === key || (wildcard && key === 0)) &&
        (a.button === button || (wildcard && button === 0)) &&
        (a.callback === callback || callback === undefined)
      )
    })

    this.actionList = actionList
  }

  /**
   * Set mouse action preset
   * @param  {String} name - one of "default", "pymol", "coot"
   * @return {undefined}
   */
  preset (name) {
    this.clear()

    const list = MouseActionPresets[ name ] || []

    list.forEach(action => this.add(...action))
  }

  /**
   * Remove all mouse actions
   * @return {undefined}
   */
  clear () {
    this.actionList.length = 0
  }
}

export default MouseControls