API Docs for: 2.0.0

src/node-interaction.js

// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2014

/**
 * @module server
 * @submodule interaction
 */

// require variables to be declared
"use strict";

var MODE = {WINDOW_MANAGEMENT: 0, APP_INTERACTION: 1};

/**
 * @class Interaction
 * @constructor
 */

function Interaction(config) {
	this.selectedMoveItem    = null;
	this.selectedScrollItem  = null;
	this.selectedResizeItem  = null;
	this.selectedMoveControl = null;
	this.previousInteractionItem = null;
	this.controlLock     = null;
	this.hoverControlItem = null;
	this.hoverCornerItem = null;
	this.selectOffsetX   = 0;
	this.selectOffsetY   = 0;
	this.selectTimeId    = {};
	this.portal = null;
	this.interactionMode = MODE.WINDOW_MANAGEMENT;
	this.previousMode    = MODE.APP_INTERACTION;
	this.configuration   = config;

	this.CTRL  = false;
	this.SHIFT = false;
	this.ALT   = false;
	this.CMD   = false;
	this.CAPS  = false;
}

/**
 *@method selectMoveItem
 */

Interaction.prototype.selectMoveItem = function(moveItem, pointerX, pointerY) {
	this.selectedMoveItem    = moveItem;
	this.selectedMoveControl = null;
	// this.selectedScrollItem  = null;
	this.selectedResizeItem  = null;
	this.selectOffsetX = this.selectedMoveItem.left - pointerX;
	this.selectOffsetY = this.selectedMoveItem.top - pointerY;

	if (this.selectedMoveItem.previous_left === null) {
		this.selectedMoveItem.previous_left = this.selectedMoveItem.left;
	}
	if (this.selectedMoveItem.previous_top === null) {
		this.selectedMoveItem.previous_top = this.selectedMoveItem.top;
	}
	if (this.selectedMoveItem.previous_width === null) {
		this.selectedMoveItem.previous_width = this.selectedMoveItem.width;
	}
	if (this.selectedMoveItem.previous_height === null) {
		this.selectedMoveItem.previous_height = this.selectedMoveItem.height;
	}
};

/**
 *@method selectMoveControl
 */

Interaction.prototype.selectMoveControl = function(moveControl, pointerX, pointerY) {
	this.selectedMoveItem    = null;
	this.selectedMoveControl = moveControl;
	this.selectedScrollItem  = null;
	this.selectedResizeItem  = null;
	this.selectOffsetX       = this.selectedMoveControl.left - pointerX;
	this.selectOffsetY       = this.selectedMoveControl.top - pointerY;
};

/**
 *@method releaseControl
 */

Interaction.prototype.releaseControl = function() {
	// Same as release item, has been created for clarity of code
	this.selectedMoveControl = null;
};


/**
 *@method selectScrollItem
 */

Interaction.prototype.selectScrollItem = function(scrollItem) {
	this.selectedMoveItem    = null;
	this.selectedScrollItem  = scrollItem;
	this.selectedResizeItem  = null;
	this.selectedMoveControl = null;
};

/**
 *@method releaseItem
 */

Interaction.prototype.releaseItem = function(valid) {
	var updatedItem = null;
	if (!valid && this.selectedMoveItem !== null) {
		this.selectedMoveItem.left   = this.selectedMoveItem.previous_left;
		this.selectedMoveItem.top    = this.selectedMoveItem.previous_top;
		this.selectedMoveItem.width  = this.selectedMoveItem.previous_width;
		this.selectedMoveItem.height = this.selectedMoveItem.previous_height;

		updatedItem = {
			elemId: this.selectedMoveItem.id, elemLeft: this.selectedMoveItem.left,
			elemTop: this.selectedMoveItem.top, elemWidth: this.selectedMoveItem.width,
			elemHeight: this.selectedMoveItem.height, date: new Date()
		};
	}

	if (valid && this.selectedMoveItem !== null && this.selectedMoveItem.maximized === false) {
		this.selectedMoveItem.previous_left   = null;
		this.selectedMoveItem.previous_top    = null;
		this.selectedMoveItem.previous_width  = null;
		this.selectedMoveItem.previous_height = null;
	}

	this.selectedMoveItem   = null;
	this.selectedScrollItem = null;
	this.selectedResizeItem = null;
	return updatedItem;
};


/**
 *@method moveSelectedItem
 */

Interaction.prototype.moveSelectedItem = function(pointerX, pointerY) {
	if (this.selectedMoveItem === null) {
		return null;
	}

	// testing maximized item slide constrained by edges of screen
	// this.selectedMoveItem.left = pointerX + this.selectOffsetX;
	// this.selectedMoveItem.top  = pointerY + this.selectOffsetY;
	// this.selectedMoveItem.maximized = false;

	// save the bottom and right coordinates for later use
	let botCoord = this.selectedMoveItem.top + this.selectedMoveItem.height;
	let rightCoord = this.selectedMoveItem.left + this.selectedMoveItem.width;

	if (!this.selectedMoveItem.maximized) {
		// move window as normal
		this.selectedMoveItem.left = pointerX + this.selectOffsetX;
		this.selectedMoveItem.top  = pointerY + this.selectOffsetY;

	} else {
		// if it is maximized
		if (this.selectedMoveItem.maximizeConstraint === "width") {
			// if maximization is constrained by width
			// only translate vertically
			this.selectedMoveItem.left = 0;
			this.selectedMoveItem.top  = pointerY + this.selectOffsetY;

			this.selectedMoveItem.previous_top =
				this.selectedMoveItem.top + this.selectedMoveItem.height / 2 -
				this.selectedMoveItem.previous_height / 2;

		} else if (this.selectedMoveItem.maximizeConstraint === "height") {
			// if maximization is constrained by height
			// only translate horizontally
			this.selectedMoveItem.left = pointerX + this.selectOffsetX;
			this.selectedMoveItem.top  = this.configuration.ui.titleBarHeight;

			this.selectedMoveItem.previous_left =
				this.selectedMoveItem.left + this.selectedMoveItem.width / 2 -
				this.selectedMoveItem.previous_width / 2;
		} else {
			// move window as normal
			// possible to change the way this works at later time
			this.selectedMoveItem.left = pointerX + this.selectOffsetX;
			this.selectedMoveItem.top  = pointerY + this.selectOffsetY;
		} // end if maximizeConstraint === ...

	} // end if maximized ...


	// if it is a snapped partition, subtract the X, Y movement from width, height
	if (this.selectedMoveItem.isSnapping && this.selectedMoveItem.partitionList) {
		this.selectedMoveItem.width = rightCoord - this.selectedMoveItem.left;
		this.selectedMoveItem.height = botCoord - this.selectedMoveItem.top;

		// enforce min width
		if (this.selectedMoveItem.width < this.selectedMoveItem.partitionList.minSize.width) {
			this.selectedMoveItem.width = this.selectedMoveItem.partitionList.minSize.width;
			this.selectedMoveItem.left = rightCoord - this.selectedMoveItem.width;
		}

		// enforce min height
		if (this.selectedMoveItem.height < this.selectedMoveItem.partitionList.minSize.height) {
			this.selectedMoveItem.height = this.selectedMoveItem.partitionList.minSize.height;
			this.selectedMoveItem.top = botCoord - this.selectedMoveItem.height;
		}
	}

	return {
		elemId: this.selectedMoveItem.id, elemLeft: this.selectedMoveItem.left,
		elemTop: this.selectedMoveItem.top, elemWidth: this.selectedMoveItem.width,
		elemHeight: this.selectedMoveItem.height, date: new Date()
	};
};

/**
 *@method moveSelectedControl
 */

Interaction.prototype.moveSelectedControl = function(pointerX, pointerY) {
	if (this.selectedMoveControl === null) {
		return null;
	}

	this.selectedMoveControl.left = pointerX + this.selectOffsetX;
	this.selectedMoveControl.top  = pointerY + this.selectOffsetY;

	return {
		elemId: this.selectedMoveControl.id, appId: this.selectedMoveControl.appId,
		elemLeft: this.selectedMoveControl.left, elemTop: this.selectedMoveControl.top,
		elemWidth: this.selectedMoveControl.width, elemHeight: this.selectedMoveControl.height,
		elemBarHeight: this.selectedMoveControl.barHeight, hasSideBar: this.selectedMoveControl.hasSideBar,
		date: Date.now()
	};
};


/**
 *@method lockedControl
 */

Interaction.prototype.lockedControl = function() {
	return this.controlLock;
};

/**
 *@method lockControl
 */

Interaction.prototype.lockControl = function(ctrl) {
	this.controlLock = ctrl;
};

/**
 *@method hoverOverControl
 */

Interaction.prototype.hoverOverControl = function() {
	return this.hoverControlItem;
};

/**
 *@method leaveControlArea
 */

Interaction.prototype.leaveControlArea = function() {
	this.hoverControlItem = null;
};

/**
 *@method enterControlArea
 */

Interaction.prototype.enterControlArea = function(controlItem) {
	this.hoverControlItem = controlItem;
};

/**
 *@method pressOnItem
 */

Interaction.prototype.pressOnItem = function(item) {
	this.pressedItem = item;
};

/**
 *@method releaseOnItem
 */

Interaction.prototype.releaseOnItem = function() {
	var item = this.pressedItem;
	this.pressedItem = null;
	return item;
};


/**
 *@method dropControl
 */

Interaction.prototype.dropControl = function() {
	this.controlLock = null;
};

/**
 *@method scrollSelectedItem
 */

Interaction.prototype.scrollSelectedItem = function(scale) {
	if (this.selectedScrollItem === null) {
		return null;
	}

	var iWidth = this.selectedScrollItem.width * scale;
	var iHeight = iWidth / this.selectedScrollItem.aspect;
	if (iWidth < this.configuration.ui.minWindowWidth) {
		iWidth  = this.configuration.ui.minWindowWidth;
		iHeight = iWidth / this.selectedScrollItem.aspect;
	}
	if (iWidth > this.configuration.ui.maxWindowWidth) {
		iWidth  = this.configuration.ui.maxWindowWidth;
		iHeight = iWidth / this.selectedScrollItem.aspect;
	}
	if (iHeight < this.configuration.ui.minWindowHeight) {
		iHeight = this.configuration.ui.minWindowHeight;
		iWidth  = iHeight * this.selectedScrollItem.aspect;
	}
	if (iHeight > this.configuration.ui.maxWindowHeight) {
		iHeight = this.configuration.ui.maxWindowHeight;
		iWidth  = iHeight * this.selectedScrollItem.aspect;
	}
	var iCenterX = this.selectedScrollItem.left + (this.selectedScrollItem.width / 2);
	var iCenterY = this.selectedScrollItem.top + (this.selectedScrollItem.height / 2);

	this.selectedScrollItem.left   = iCenterX - (iWidth / 2);
	this.selectedScrollItem.top    = iCenterY - (iHeight / 2);
	this.selectedScrollItem.width  = iWidth;
	this.selectedScrollItem.height = iHeight;

	this.selectedScrollItem.maximized = false;

	return {
		elemId: this.selectedScrollItem.id, elemLeft: this.selectedScrollItem.left,
		elemTop: this.selectedScrollItem.top, elemWidth: this.selectedScrollItem.width,
		elemHeight: this.selectedScrollItem.height, date: new Date()
	};
};

/**
 *@method setHoverCornerItem
 */

Interaction.prototype.setHoverCornerItem = function(item) {
	this.hoverCornerItem = item;
};

/**
 *@method selectResizeItem
 */

Interaction.prototype.selectResizeItem = function(resizeItem, pointerX, pointerY) {
	this.selectedMoveItem    = null;
	// this.selectedScrollItem  = null;
	this.selectedMoveControl = null;
	this.selectedResizeItem  = resizeItem;
	this.selectOffsetX       = this.selectedResizeItem.width  - (pointerX - this.selectedResizeItem.left);
	this.selectOffsetY       = this.selectedResizeItem.height - (pointerY - this.selectedResizeItem.top);
};

/**
 *@method resizeSelectedItem
 */

Interaction.prototype.resizeSelectedItem = function(pointerX, pointerY) {
	if (this.selectedResizeItem === null) {
		return null;
	}

	// save the bottom and right coordinates for later use
	let botCoord = this.selectedResizeItem.top + this.selectedResizeItem.height;
	let rightCoord = this.selectedResizeItem.left + this.selectedResizeItem.width;


	var iWidth  = pointerX - this.selectedResizeItem.left + this.selectOffsetX;
	var iHeight = 1;
	var resizeMode = this.SHIFT;

	// Flip the resize mode if resize app preference is 'free'
	if (this.selectedResizeItem.resizeMode === "free") {
		resizeMode = !resizeMode;
	}

	if (resizeMode === true) {
		iHeight = pointerY - this.selectedResizeItem.top + this.selectOffsetY;

		if (iWidth  < this.configuration.ui.minWindowWidth) {
			iWidth  = this.configuration.ui.minWindowWidth;
		}
		if (iHeight < this.configuration.ui.minWindowHeight) {
			iHeight = this.configuration.ui.minWindowHeight;
		}

		if (iWidth  > this.configuration.ui.maxWindowWidth) {
			iWidth  = this.configuration.ui.maxWindowWidth;
		}
		if (iHeight > this.configuration.ui.maxWindowHeight) {
			iHeight = this.configuration.ui.maxWindowHeight;
		}

		this.selectedResizeItem.aspect = iWidth / iHeight;
	} else {
		iHeight = iWidth / this.selectedResizeItem.aspect;
		if (iWidth < this.configuration.ui.minWindowWidth) {
			iWidth  = this.configuration.ui.minWindowWidth;
			iHeight = iWidth / this.selectedResizeItem.aspect;
		}
		if (iWidth > this.configuration.ui.maxWindowWidth) {
			iWidth  = this.configuration.ui.maxWindowWidth;
			iHeight = iWidth / this.selectedResizeItem.aspect;
		}
		if (iHeight < this.configuration.ui.minWindowHeight) {
			iHeight = this.configuration.ui.minWindowHeight;
			iWidth  = iHeight * this.selectedResizeItem.aspect;
		}
		if (iHeight > this.configuration.ui.maxWindowHeight) {
			iHeight = this.configuration.ui.maxWindowHeight;
			iWidth  = iHeight * this.selectedResizeItem.aspect;
		}
	}

	if (this.selectedResizeItem.partition) {
		// if the item is in a partition
		if (this.selectedResizeItem.maximized) {
			// and it is maximized
			// cancel maximized state of partition
			this.selectedResizeItem.partition.innerMaximization = false;
		}
		if (this.selectedResizeItem.partition.innerTiling) {
			// if the partition is tiled
			// cancel the tiled state of the partition
			this.selectedResizeItem.partition.toggleInnerTiling();
		}
	}

	this.selectedResizeItem.width  = iWidth;
	this.selectedResizeItem.height = iHeight;
	this.selectedResizeItem.maximized = false;


	// if it is a partition enforce minimum size constraints
	if (this.selectedResizeItem.partitionList) {
		// enforce min width
		if (this.selectedResizeItem.width < this.selectedResizeItem.partitionList.minSize.width) {
			this.selectedResizeItem.width = this.selectedResizeItem.partitionList.minSize.width;
			this.selectedResizeItem.left = rightCoord - this.selectedResizeItem.width;
		}

		// enforce min height
		if (this.selectedResizeItem.height < this.selectedResizeItem.partitionList.minSize.height) {
			this.selectedResizeItem.height = this.selectedResizeItem.partitionList.minSize.height;
			this.selectedResizeItem.top = botCoord - this.selectedResizeItem.height;
		}
	}

	return {
		elemId: this.selectedResizeItem.id, elemLeft: this.selectedResizeItem.left,
		elemTop: this.selectedResizeItem.top, elemWidth: this.selectedResizeItem.width,
		elemHeight: this.selectedResizeItem.height, date: new Date()
	};
};

/**
 *@method maximizeSelectedItem
 */

Interaction.prototype.maximizeSelectedItem = function(item, centered) {
	if (item === null) {
		return null;
	}

	// normally is just the screen size, but if in partition it is the partition boundaries
	var maxBound = {
		left: 0,
		top: 0,
		width: 0,
		height: 0
	};

	var titleBar = this.configuration.ui.titleBarHeight;
	if (this.configuration.ui.auto_hide_ui === true) {
		titleBar = 0;
	}

	if (item.partitionList && item.isSnapping) {
		// if the item is a partition which is snapped, make it as large as it can get (keeping neighbor positions correct)
		let newSize = item.getMovementBoundaries();

		// back up values for restore
		item.previous_left   = item.left;
		item.previous_top    = item.top;
		item.previous_width  = item.width;
		item.previous_height = item.width / item.aspect;

		item.left = newSize.left.min;
		item.top = newSize.top.min;
		item.width = newSize.right.max - item.left;
		item.height = newSize.bottom.max - item.top;

		item.maximized = true;

	} else if (item.partition) {
		// if the item is content in a partition
		return item.partition.maximizeChild(item.id, this.SHIFT);
	} else {
		// normal wall maximization parameters
		maxBound.left = 0;
		maxBound.top = 0;
		maxBound.width = this.configuration.totalWidth;
		maxBound.height = this.configuration.totalHeight;

		var outerRatio = maxBound.width  / maxBound.height;
		var iCenterX  = centered ? maxBound.left + maxBound.width / 2.0 : item.left + item.width / 2.0;
		var iCenterY  = centered ? maxBound.top + maxBound.height / 2.0 : item.top + item.height / 2.0;
		var iWidth    = 1;
		var iHeight   = 1;


		if (this.SHIFT === true && item.resizeMode === "free") {
			// previously would resize to native height/width
			// item.aspect = item.native_width / item.native_height;

			// Free Resize aspect ratio fills wall
			iWidth = maxBound.width;
			iHeight = maxBound.height - (2 * titleBar);
			item.maximizeConstraint = "none";
		} else {
			if (item.aspect > outerRatio) {
				// Image wider than wall area
				iWidth  = maxBound.width;
				iHeight = iWidth / item.aspect;
				item.maximizeConstraint = "width";
			} else {
				// Wall area than image
				iHeight = maxBound.height - (2 * titleBar);
				iWidth  = iHeight * item.aspect;
				item.maximizeConstraint = "height";
			}
		}

		// back up values for restore
		item.previous_left   = item.left;
		item.previous_top    = item.top;
		item.previous_width  = item.width;
		item.previous_height = item.width / item.aspect;

		// calculate new values
		item.top    = iCenterY - (iHeight / 2);
		item.width  = iWidth;
		item.height = iHeight;

		// keep window inside display horizontally
		if (iCenterX - (iWidth / 2) < maxBound.left) {
			item.left = maxBound.left;
		} else if (iCenterX + (iWidth / 2) > maxBound.left + maxBound.width) {
			item.left = maxBound.width + maxBound.left - iWidth;
		} else {
			item.left = iCenterX - (iWidth / 2);
		}

		// keep window inside display vertically
		if (iCenterY - (iHeight / 2) < maxBound.top + titleBar) {
			item.top = maxBound.top + titleBar;
		} else if (iCenterY + (iHeight / 2) > maxBound.top + maxBound.height) {
			item.top = maxBound.top + maxBound.height - iHeight - titleBar;
		} else {
			item.top = iCenterY - (iHeight / 2);
		}

		// Shift by 'titleBarHeight' if no auto-hide
		if (this.configuration.ui.auto_hide_ui === true) {
			item.top = item.top - this.configuration.ui.titleBarHeight;
		}

		item.maximized = true;

		return {
			elemId: item.id, elemLeft: item.left, elemTop: item.top,
			elemWidth: item.width, elemHeight: item.height, date: new Date()
		};
	}
};

Interaction.prototype.maximizeFullSelectedItem = function(item) {
	if (item === null) {
		return null;
	}

	// back up values for restore
	item.previous_left   = item.left;
	item.previous_top    = item.top;
	item.previous_width  = item.width;
	item.previous_height = item.width / item.aspect;

	// calculate new values
	if (this.configuration.ui.auto_hide_ui === true) {
		item.left   = 0;
		item.top    = -this.configuration.ui.titleBarHeight;
		item.width  = this.configuration.totalWidth;
		item.height = this.configuration.totalHeight;
	} else {
		item.left   = 0;
		item.top    = this.configuration.ui.titleBarHeight;
		item.width  = this.configuration.totalWidth;
		item.height = this.configuration.totalHeight - 2 * this.configuration.ui.titleBarHeight;
	}

	item.maximized = true;

	return {
		elemId: item.id, elemLeft: item.left, elemTop: item.top,
		elemWidth: item.width, elemHeight: item.height, date: new Date()
	};
};


/**
 *@method restoreSelectedItem
 */
Interaction.prototype.restoreSelectedItem = function(item) {
	if (item === null) {
		return null;
	}

	if (item.partition) {
		return item.partition.restoreChild(item.id, this.SHIFT);
	}

	if (this.SHIFT === true) {
		// resize to native width/height
		item.aspect = item.native_width / item.native_height;
		item.left = item.previous_left + item.previous_width / 2 - item.native_width / 2;
		item.top = item.previous_top + item.previous_height / 2 - item.native_height / 2;
		item.width = item.native_width;
		item.height = item.native_height;
	} else {
		item.left   = item.previous_left;
		item.top    = item.previous_top;
		item.width  = item.previous_width;
		item.height = item.previous_height;
	}

	item.maximized = false;

	return {
		elemId: item.id, elemLeft: item.left, elemTop: item.top,
		elemWidth: item.width, elemHeight: item.height, date: new Date()
	};
};

/**
 *@method isWindowManagementMode
 */
Interaction.prototype.isWindowManagementMode = function() {
	return this.interactionMode === MODE.WINDOW_MANAGEMENT;
};

/**
 *@method isAppInteractionMode
 */
Interaction.prototype.isAppInteractionMode = function() {
	return this.interactionMode === MODE.APP_INTERACTION;
};

/**
 *@method selectWindowManagementMode
 */
Interaction.prototype.selectWindowManagementMode = function() {
	this.interactionMode = MODE.WINDOW_MANAGEMENT;

	this.selectedMoveItem   = null;
	this.selectedScrollItem = null;
	this.selectedResizeItem = null;
};

/**
 *@method selectAppInteractionMode
 */
Interaction.prototype.selectAppInteractionMode = function() {
	this.interactionMode = MODE.APP_INTERACTION;

	this.selectedMoveItem   = null;
	this.selectedScrollItem = null;
	this.selectedResizeItem = null;
};

/**
 *@method toggleModes
 */
Interaction.prototype.toggleModes = function() {
	if (this.interactionMode === MODE.WINDOW_MANAGEMENT) {
		this.interactionMode = MODE.APP_INTERACTION;
	} else if (this.interactionMode ===  MODE.APP_INTERACTION) {
		this.interactionMode = MODE.WINDOW_MANAGEMENT;
	}

	this.selectedMoveItem   = null;
	this.selectedScrollItem = null;
	this.selectedResizeItem = null;
};

/**
 *@method windowManagementMode
 */
Interaction.prototype.windowManagementMode = function() {
	return this.interactionMode === MODE.WINDOW_MANAGEMENT;
};

/**
 *@method appInteractionMode
 */
Interaction.prototype.appInteractionMode = function() {
	return this.interactionMode === MODE.APP_INTERACTION;
};

/**
 *@method setPreviousInteractionItem
 */
Interaction.prototype.setPreviousInteractionItem = function(item) {
	this.previousInteractionItem = item;
};

/**
 *@method getPreviousInteractionItem
 */
Interaction.prototype.getPreviousInteractionItem = function() {
	return this.previousInteractionItem;
};

/**
 *@method getPreviousMode
 */
Interaction.prototype.getPreviousMode = function() {
	return this.previousMode;
};

/**
 *@method getPreviousMode
 */
Interaction.prototype.saveMode = function() {
	this.previousMode = this.interactionMode;
};

module.exports = Interaction;