matrix/widgets/indicator.js

/*
 * This file is part of AUX.
 *
 * AUX is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * AUX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General
 * Public License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

import { addClass, toggleClass } from './../../utils/dom.js';
import { Subscriptions } from '../../utils/subscriptions.js';

import { Button } from './../../widgets/button.js';
import { defineRender } from '../../renderer.js';

function pointerEnter(e) {
  this.sink.set('hovered', true);
  this.source.set('hovered', true);
  this.parent.parent.emit('indicatorEnter', {
    sink: this.sink,
    source: this.source,
    event: e,
    indicator: this,
  });
}
function pointerLeave(e) {
  this.sink.set('hovered', false);
  this.source.set('hovered', false);
  this.parent.parent.emit('indicatorLeave', {
    sink: this.sink,
    source: this.source,
    event: e,
    indicator: this,
  });
}

/**
 * Indicator is a button element inside the {@link Indicators} widget
 * for the {@link Matrix}. All properties are reflected as class onto
 * the element.
 *
 * @param {Object} [options={ }] - An object containing initial options.
 *
 * @property {Boolean} [options.connected] - Sink and source represented by this
 *   indicator are connected.
 * @property {Boolean} [options.connectable] - Sink and source represented by
 *   this indicator can be connected, no restrictions.
 * @property {Boolean} [options.sourceisgroup] - The source is a group header.
 * @property {Boolean} [options.sinkisgroup] - The sink is a group header.
 * @property {Boolean} [options.isgroup] - Either source or sink is a group header.
 *
 * @extends Button
 *
 * @class Indicator
 */

export class Indicator extends Button {
  static get _options() {
    return {
      connected: 'boolean',
      connectable: 'boolean',
      sourceisgroup: 'boolean',
      sinkisgroup: 'boolean',
      isgroup: 'boolean',
    };
  }

  static get static_events() {
    return {
      pointerleave: pointerLeave,
      pointerenter: pointerEnter,
    };
  }

  static get renderers() {
    return [
      defineRender('connected', function (connected) {
        toggleClass(this.element, 'aux-connected', connected);
      }),
      defineRender('connectable', function (connectable) {
        toggleClass(this.element, 'aux-connectable', connectable);
      }),
      defineRender('sourceisgroup', function (sourceisgroup) {
        toggleClass(this.element, 'aux-sourceisgroup', sourceisgroup);
      }),
      defineRender('sinkisgroup', function (sinkisgroup) {
        toggleClass(this.element, 'aux-sinkisgroup', sinkisgroup);
      }),
      defineRender('isgroup', function (isgroup) {
        toggleClass(this.element, 'aux-isgroup', isgroup);
      }),
    ];
  }

  initialize(options) {
    super.initialize(options);
    this.source = null;
    this.sink = null;
    this.connection = null;
    this.source_subscriptions = new Subscriptions();
    this.sink_subscriptions = new Subscriptions();
    this.connection_subscriptions = new Subscriptions();
  }

  /**
   * Updates the Indicator with the relevant data.
   *
   * @method updateData
   *
   * @param {Integer} index1 - The index of the entry in the first list.
   * @param {Integer} index2 - The index of the entry in the second list.
   * @param {ConnectionData} connection - The connection data.
   * @param {PortData} source - The source data.
   * @param {PortData} sink - The sink data.
   *
   */
  updateData(index1, index2, connection, source, sink, connectionView) {
    this.update('connected', !!connection);
    this.update('sourceisgroup', source && source.isGroup);
    this.update('sinkisgroup', sink && sink.isGroup);
    this.update(
      'isgroup',
      (sink && sink.isGroup) || (source && source.isGroup)
    );

    const connectable = source && sink && !source.isGroup && !sink.isGroup;
    this.update('connectable', connectable);

    this.source = source;
    this.sink = sink;
    this.connection = connection;

    this.maybeSubscribeData(
      source,
      this.source_subscriptions,
      '_onSourceChanged'
    );
    this.maybeSubscribeData(sink, this.sink_subscriptions, '_onSinkChanged');

    if (!connection) return;

    if (connection instanceof Set) {
      connection.forEach((connection) => {
        this.maybeSubscribeData(
          connection,
          this.connection_subscriptions,
          '_onConnectionChanged'
        );
      });
    } else {
      this.maybeSubscribeData(
        connection,
        this.connection_subscriptions,
        '_onConnectionChanged'
      );
    }
  }

  maybeSubscribeData(data, subscriptions, methodName) {
    subscriptions.unsubscribe();
    if (!data || !this[methodName]) return;

    subscriptions.add(
      data.subscribe('propertyChanged', (key, value) => {
        this[methodName](key, value, data);
      })
    );
  }

  draw(options, element) {
    addClass(this.element, 'aux-indicator');
    super.draw(options, element);
  }

  destroy() {
    this.source_subscriptions.unsubscribe();
    this.sink_subscriptions.unsubscribe();
    this.connection_subscriptions.unsubscribe();
    super.destroy();
  }
}