(function() {
/**
* @typedef {Object} Help4.widget.help.view2.LaserBeam.Params
* @property {Help4.widget.help.Widget} widget
* @property {Help4.control2.container.Container} contentView
* @property {Help4.control2.container.Container} tileView
* @property {Help4.widget.help.view2.Tile.Descriptor} descriptor
*/
/**
* Laser beam control to connect tile and hotspot.
* @augments Help4.jscore.ControlBase
* @property {Help4.widget.help.Widget} __widget
* @property {Help4.control2.container.Container} __contentView
* @property {Help4.control2.container.Container} __tileView
* @property {Help4.widget.help.view2.Tile.Descriptor} descriptor
* @property {?Help4.control2.hotspot.Connected} _hotspotControl
*/
Help4.widget.help.view2.LaserBeam = class extends Help4.jscore.ControlBase {
/**
* @constructor
* @param {Help4.widget.help.view2.LaserBeam.Params} params
*/
constructor(params) {
const {TYPES: T} = Help4.jscore.ControlBase;
super(params, {
params: {
widget: {type: T.instance, mandatory: true, readonly: true, private: true},
contentView: {type: T.instance, mandatory: true, readonly: true, private: true},
tileView: {type: T.instance, mandatory: true, readonly: true, private: true},
descriptor: {type: T.string, mandatory: true}
},
statics: {
_hotspotControl: {destroy: false}
}
});
_show.call(this);
}
/**
* @param {Help4.widget.help.view2.View} view
* @param {?Help4.widget.help.view2.Tile.Descriptor} [descriptor = null]
*/
static show(view, descriptor = null) {
const {__widget, _laserBeam, _contentView, _tileView} = view;
const {showLaserBeam} = __widget.getContext().configuration.help;
if (!showLaserBeam) return;
if (!descriptor && _laserBeam) descriptor = _laserBeam.descriptor;
if (descriptor) {
_laserBeam?.destroy(); // destroy possible previous beam
const {LaserBeam} = Help4.widget.help.view2;
view._laserBeam = /** @type {Help4.widget.help.view2.LaserBeam} */ new LaserBeam({
widget: __widget,
contentView: _contentView,
tileView: _tileView,
descriptor
});
}
}
/**
* @param {Help4.widget.help.view2.View} view
* @param {Help4.widget.help.view2.Tile.Descriptor} descriptor
*/
static hide(view, descriptor) {
const {_laserBeam} = view;
if (!_laserBeam) return;
// only remove laser beam if correct tile descriptor
if (descriptor === _laserBeam.descriptor) {
_laserBeam.destroy();
view._laserBeam = null;
}
}
/** @override */
destroy() {
this._hotspotControl?.cleanConnections();
super.destroy();
}
}
/**
* @memberof Help4.widget.help.view2.LaserBeam#
* @private
*/
function _show() {
const {__widget, __contentView, __tileView, descriptor} = this;
const {
/** @type {Help4.control2.bubble.Panel} */ panel,
/** @type {Help4.typedef.SystemConfiguration} */ configuration
} = /** @type {Help4.widget.help.Widget.Context} */ __widget.getContext();
const tileControl = /** @type {?Help4.control2.Tile} */ __tileView.get({byMetadata: {descriptor, type: 'tile'}});
const hotspotControl = /** @type {?Help4.control2.hotspot.Connected} */ __contentView.get({byMetadata: {descriptor, type: 'hotspot'}});
if (tileControl && hotspotControl) {
const area = /** @type {Help4.control2.AreaXYWH} */ panel.getArea();
const pcp = /** @type {Help4.control2.ConnectionPoints} */ panel.getConnectionPoints();
const {
/** @type {Help4.control2.PositionXY} */ m
} = /** @type {Help4.control2.ConnectionPoints} */ hotspotControl.getConnectionPoints();
/** @type {?Help4.control2.PositionXY} */
const point = panel.docked
? (panel.rtl ? pcp.r : pcp.l)
: _getPoint(m, area, pcp);
if (point) {
const connectionParams = {
...Help4.HOTSPOT_CONNECTION_DEFAULTS,
svg: configuration.core.isRemoteMode,
point
};
hotspotControl.addConnection(connectionParams);
this._hotspotControl = hotspotControl;
}
}
}
/**
* @memberof Help4.widget.help.view2.LaserBeam#
* @private
* @param {Help4.control2.PositionXY} hotspotPoint
* @param {Help4.control2.AreaXYWH} panelArea
* @param {Help4.control2.ConnectionPoints} panelConnectionPoints
* @returns {?Help4.control2.PositionXY}
*/
function _getPoint(hotspotPoint, panelArea, panelConnectionPoints) {
if (Help4.isPointInRect(hotspotPoint, panelArea)) {
// hotspot mid-point is below panel
return null; // XXX: add support
} else {
// create all possible lines from panel to hotspot
const lines = {};
['l', 't', 'r', 'b'].forEach(pos => {
// line from panel to hotspot
const line = {
x1: hotspotPoint.x,
y1: hotspotPoint.y,
x2: panelConnectionPoints[pos].x,
y2: panelConnectionPoints[pos].y
};
// only if it does not cross the panel
if (!Help4.getRectIntersect(panelArea, line, -1)) {
lines[pos] = line;
}
});
const keys = Object.keys(lines);
const nbr = keys.length;
// just one line does not touch the panel
if (nbr === 1) return panelConnectionPoints[keys[0]];
// multiple lines do not touch the panel
if (nbr > 1) {
// calculate line distances
const distances = {};
keys.forEach(key => {
const {x1, y1, x2, y2} = lines[key];
distances[key] = Math.pow(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2), 0.5);
});
// select the shortest line
/** @type {number[]} */ const values = Object.values(distances);
const min = Math.min(...values);
const idx = values.indexOf(min);
const key = Object.keys(distances)[idx];
return panelConnectionPoints[key];
}
// every line touches the panel
return null;
}
}
})();