public/src/radialMenu.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-2015
"use strict";
/**
* Menu System for SAGE2 Display Clients
*
* @module client
* @submodule RadialMenu
*/
// layout parameters (Defaults based on Cyber-Commons touch interaction)
var imageThumbSize = 75;
var thumbSpacer = 5;
// var thumbnailWindowWidth = 0.8;
var previewWindowWidth = 0.2;
// var previewWindowOffset = 0.74;
var radialMenuCenter = { x: 215, y: 215 }; // scaled in init - based on window size
var angleSeparation = 36;
var initAngle = 90;
var angle = 0;
var menuRadius = 100;
var menuButtonSize = 100; // pie image size
var menuButtonHitboxSize = 50;
var overlayIconScale = 0.5; // image, pdf, etc image
var thumbnailScrollScale = 1;
var thumbnailDisableSelectionScrollDistance = 5; // Distance in pixels scroll window can move before button select is cancelled
var thumbnailWindowSize = { x: 1024, y: 768 };
var thumbnailPreviewWindowSize = { x: 550, y: 800 };
var radialMenuList = {};
// Mostly for debugging, toggles buttons/thumbnails redrawing on a events (like move)
// var enableEventRedraw = false;
// common radial menu icons
var radialMenuIcons = {};
// Creates an image and adds to dictionary with image path as key
function loadImageIcon(src) {
var newIcon = new Image();
newIcon.src = src;
radialMenuIcons[src] = newIcon;
}
var radialButtonIcon = new Image();
radialButtonIcon.src = "images/radialMenu/icon_radial_button_circle.svg";
var radialDragIcon = new Image();
radialDragIcon.src = "images/radialMenu/drag-ring.svg";
var radialMenuLevel2Icon = new Image();
radialMenuLevel2Icon.src = "images/radialMenu/icon_radial_level2_360.png";
// Level 1 radial icons
loadImageIcon("images/ui/close.svg");
loadImageIcon("images/ui/remote.svg");
loadImageIcon("images/ui/pdfs.svg");
loadImageIcon("images/ui/images.svg");
loadImageIcon("images/ui/videos.svg");
loadImageIcon("images/ui/applauncher.svg");
loadImageIcon("images/ui/loadsession.svg");
loadImageIcon("images/ui/savesession.svg");
loadImageIcon("images/ui/arrangement.svg");
// Level 2 radial icons
loadImageIcon("images/ui/clearcontent.svg");
loadImageIcon("images/ui/tilecontent.svg");
/**
* Radial menu and Thumbnail Content Window
* @class RadialMenu
* @constructor
*/
function RadialMenu() {
this.element = null;
this.ctx = null;
this.thumbnailScrollWindowElement = null;
this.thumbScrollWindowctx = null;
this.thumbnailScrollWindowElement2 = null;
this.thumbScrollWindowctx2 = null;
this.thumbnailWindowSize = thumbnailWindowSize;
this.imageThumbSize = imageThumbSize;
// This is the number of thumbnails in the window WITHOUT scrolling
this.thumbnailGridSize = { x: 10, y: 10 }; // Overwritten in setThumbnailPosition().
this.level0Buttons = [];
this.level1Buttons = [];
this.level2Buttons = [];
this.radialMenuButtons = {};
this.thumbnailWindows = {};
this.thumbnailButtons = [];
this.imageThumbnailButtons = [];
this.videoThumbnailButtons = [];
this.pdfThumbnailButtons = [];
this.appThumbnailButtons = [];
this.sessionThumbnailButtons = [];
/**
* Helper function for creating radial menu buttons
*
* @method addRadialMenuButton
* @param name {String} name of the button
* @param icon {Image} icon image for the button
* @param iconScale {Float} scale factor for icon
* @param dim {{buttonSize: float, hitboxSize: float}} object specifying the button display and hitbox size
* @param alignment {String} where the center of the button is defined 'left' (default) or 'centered'
* @param radialAnglePos {Float} position of the button along the radius. based on angleSeparation and initAngle
* @param radialLevel {Float} radial level of button (0 = center, 1 = standard icons, 2 = secondary icons)
* @return {ButtonWidget} the ButtonWidget object created
*/
this.addRadialMenuButton = function(name, icon, iconScale, dim, alignment, radialAnglePos, radialLevel) {
var button;
if (radialLevel === 0) {
button = this.createRadialButton(radialButtonIcon, false, dim.buttonSize, dim.hitboxSize,
alignment, dim.shape, radialAnglePos, 0);
button.setOverlayImage(icon, iconScale);
button.isLit = true; // Button will stay lit regardless of hover-over
this.level0Buttons.push(button);
} else if (radialLevel === 1) {
button = this.createRadialButton(radialButtonIcon, false, dim.buttonSize, dim.hitboxSize,
alignment, dim.shape, radialAnglePos, menuRadius);
button.setOverlayImage(icon, iconScale);
button.isLit = true; // Button will stay lit regardless of hover-over
this.level1Buttons.push(button);
} else if (radialLevel === 2) {
button = this.createRadialButton(radialButtonIcon, false, dim.buttonSize, dim.hitboxSize,
alignment, dim.shape, radialAnglePos, menuRadius * 1.6);
button.setOverlayImage(icon, iconScale);
button.isLit = true; // Button will stay lit regardless of hover-over
this.level2Buttons.push(button);
}
this.radialMenuButtons[name] = button;
return button;
};
/**
* Initialization
*
* @method init
* @param data { id: this.pointerid, x: this.left, y: this.top, radialMenuSize: this.radialMenuSize,
* thumbnailWindowSize: this.thumbnailWindowSize, radialMenuScale: this.radialMenuScale,
* visble: this.visible } Radial menu info from node-radialMenu
* @param thumbElem {Element} DOM element for the thumbnail content window
* @param thumbElem2 {Element} DOM element for the metadata window (not currently implemented)
*/
this.init = function(data, thumbElem, thumbElem2) {
this.divCtxDebug = false;
this.id = data.id;
this.radialMenuScale = data.radialMenuScale;
// overwritten in init - based on window size
this.radialMenuCenter = {
x: radialMenuCenter.x * this.radialMenuScale,
y: radialMenuCenter.y * this.radialMenuScale
};
this.radialMenuSize = data.radialMenuSize;
this.thumbnailWindowSize.x = data.thumbnailWindowSize.x;
this.thumbnailWindowSize.y = data.thumbnailWindowSize.y;
this.imageThumbSize = imageThumbSize * this.radialMenuScale;
this.textHeaderHeight = 32 * this.radialMenuScale;
// gets because pointer is assumed to be created with initial connection (else createElement( canvas tag)
this.element = document.getElementById(this.id + "_menu");
this.ctx = this.element.getContext("2d");
this.resrcPath = "images/radialMenu/";
this.menuID = this.id + "_menu";
this.currentMenuState = "radialMenu";
this.currentRadialState = "radialMenu";
this.showArrangementSubmenu = false;
this.settingMenuOpen = false;
this.timer = 0;
this.menuState = "open";
this.stateTransition = 0;
this.stateTransitionTime = 1;
this.visible = true;
this.windowInteractionMode = false;
this.ctx.redraw = true;
this.dragPosition = { x: 0, y: 0 };
this.notEnoughThumbnailsToScroll = false; // Flag to stop scrolling if there are not enough thumbnails
this.dragThumbnailWindow = false;
this.thumbnailWindowPosition = {
x: (this.radialMenuCenter.x * 2 + this.imageThumbSize / 2),
y: 30 * this.radialMenuScale };
this.thumbnailWindowDragPosition = { x: 0, y: 0 };
this.thumbnailWindowScrollOffset = { x: 0, y: 0 };
this.thumbnailWindowInitialScrollOffset = { x: 0, y: 0 };
this.maxThumbnailScrollDistance = 0;
this.radialMenuDiv = document.getElementById(this.id + "_menu");
this.thumbnailWindowDiv = document.getElementById(this.id + "_menuDiv");
// Debug: Show scrolling window background
if (this.divCtxDebug) {
this.thumbnailWindowDiv.style.backgroundColor = "rgba(10,50,200,0.2)";
}
this.thumbnailScrollWindowElement = thumbElem;
this.thumbScrollWindowctx = this.thumbnailScrollWindowElement.getContext("2d");
this.thumbnailWindowScrollLock = { x: false, y: true };
this.scrollOpenContentLock = false; // Stop opening content/app if window is scrolling
this.thumbnailScrollWindowElement.width = this.thumbnailWindowSize.x - this.thumbnailWindowPosition.x;
this.thumbnailScrollWindowElement.height = this.thumbnailWindowSize.y - this.thumbnailWindowPosition.y;
this.thumbnailScrollWindowElement.style.display = "block";
this.hoverOverText = "";
radialMenuList[this.id + "_menu"] = this;
if (isMaster) {
this.wsio = wsio;
this.sendsToServer = true;
} else {
this.sendsToServer = false;
}
// Create buttons
// icon, useBackgroundColor, buttonSize, hitboxSize, alignment, hitboxType, radialAnglePos, radialDistance
this.radialDragButton = this.createRadialButton(radialDragIcon, false, 500,
this.imageThumbSize, "centered", "circle", 0, 0);
this.radialCenterButton = this.createRadialButton(radialButtonIcon, false, menuButtonSize,
menuButtonHitboxSize, "centered", "circle", 0, 0);
// Generate the radial menu buttons as specified by the server
for (var buttonName in data.layout) {
var buttonInfo = data.layout[buttonName];
this.addRadialMenuButton(buttonName, radialMenuIcons[buttonInfo.icon], overlayIconScale,
{buttonSize: menuButtonSize, hitboxSize: menuButtonHitboxSize, shape: "circle"},
"centered", buttonInfo.radialPosition, buttonInfo.radialLevel);
}
};
/**
* Sets the state of a radial menu button
*
* @method createRadialButton
* @param buttonID
* @param state
*/
this.setRadialButtonState = function(buttonID, state, color) {
if (color !== undefined) {
this.radialMenuButtons[buttonID].mouseOverColor = color;
}
this.radialMenuButtons[buttonID].setButtonState(state);
this.draw();
};
/**
* Helper function for creating a radial button (more generic than addRadialMenuButton)
*
* @method createRadialButton
* @param icon {Image} icon image for the button
* @param useBackgroundColor {Boolean}
* @param buttonSize {Float} size of the button in pixels
* @param hitboxSize {Float} size of the button hitbox in pixels
* @param alignment {String} where the center of the button is defined "left" (default) or "centered"
* @param hitboxShape {String} shape of the hitbox "box" or "circle"
* @param radialPos {Float} position of the button along the radius. based on angleSeparation and initAngle
* @param buttonRadius {Float} distance from the center of the menu
* @return {ButtonWidget} the ButtonWidget object created
*/
this.createRadialButton = function(idleIcon, useBackgroundColor, buttonSize, hitboxSize, alignment,
hitboxShape, radialPos, buttonRadius) {
var button = new ButtonWidget();
button.init(0, this.ctx, null);
button.setButtonImage(idleIcon);
button.useBackgroundColor = useBackgroundColor;
button.useEventOverColor = true;
button.setSize(buttonSize * this.radialMenuScale, buttonSize * this.radialMenuScale);
button.setHitboxSize(hitboxSize * this.radialMenuScale, hitboxSize * this.radialMenuScale);
button.alignment = alignment;
button.hitboxShape = hitboxShape;
angle = (initAngle + angleSeparation * radialPos) * (Math.PI / 180);
button.setPosition(this.radialMenuCenter.x - buttonRadius * this.radialMenuScale * Math.cos(angle),
this.radialMenuCenter.y - buttonRadius * this.radialMenuScale * Math.sin(angle));
button.setRotation(angle - Math.PI / 2);
return button;
};
/**
* Helper function for drawing an image
*
* @method drawImage
* @param ctx {Context} context to draw on
* @param image {Image} image to draw
* @param position {x: Float, y: Float} position to draw
* @param size {x: Float, y: Float} width, height of image
* @param color {Color} fill color to use
* @param rotation {Float} rotation of the image (not currently used)
* @param centered {Boolean} is the center of the image the origin for positioning
*/
this.drawImage = function(ctx, image, position, size, color, rotation, centered) {
// this.ctx.save();
ctx.fillStyle = color;
// this.ctx.translate( position.x , position.y);
// this.ctx.rotate( (initAngle + angleSeparation * angleIncrement + 90) * (Math.PI/180));
if (centered) {
ctx.drawImage(image, position.x - size.x / 2, position.y - size.y / 2, size.x, size.y);
} else {
ctx.drawImage(image, position.x, position.y, size.x, size.y);
}
// this.ctx.restore();
};
/**
* Forces a redraw of the menu
*
* @method redraw
*/
this.redraw = function() {
this.thumbScrollWindowctx.redraw = true;
this.draw();
this.drawThumbnailWindow();
};
/**
* Draws the menu
*
* @method draw
*/
this.draw = function() {
// clear canvas
this.ctx.clearRect(0, 0, this.element.width, this.element.height);
// TEMP: Just to clearly see context edge
if (this.divCtxDebug) {
this.ctx.fillStyle = "rgba(5, 255, 5, 0.3)";
this.ctx.fillRect(0, 0, this.element.width, this.element.height);
}
if (this.menuState === "opening") {
if (this.stateTransition < 1) {
this.stateTransition += this.stateTransitionTime / 1000;
} else {
this.stateTransition = 0;
}
} else if (this.menuState === "open") {
this.stateTransition = 1;
}
this.radialDragButton.draw();
if (this.currentMenuState !== "radialMenu") {
// line from radial menu to thumbnail window
this.ctx.beginPath();
this.ctx.moveTo(this.radialMenuCenter.x + menuButtonSize / 4 * this.radialMenuScale, this.radialMenuCenter.y);
this.ctx.lineTo(this.thumbnailWindowPosition.x - 18 * this.radialMenuScale, this.radialMenuCenter.y);
this.ctx.strokeStyle = "#ffffff";
this.ctx.lineWidth = 5 * this.radialMenuScale;
this.ctx.stroke();
}
// draw lines to each button
var i;
for (i = 0; i < this.level1Buttons.length; i++) {
if (this.level1Buttons[i].isHidden() === false) {
this.ctx.beginPath();
// We are adding -Math.PI/2 since angle also accounts for the initial orientation of the button image
this.ctx.moveTo(this.radialMenuCenter.x + (menuButtonSize / 4 * this.radialMenuScale) *
Math.cos(this.level1Buttons[i].angle - Math.PI / 2),
this.radialMenuCenter.y + (menuButtonSize / 4 * this.radialMenuScale) *
Math.sin(this.level1Buttons[i].angle - Math.PI / 2));
this.ctx.lineTo(this.level1Buttons[i].posX + (menuButtonSize / 4 * this.radialMenuScale) *
Math.cos(this.level1Buttons[i].angle + Math.PI / 2),
this.level1Buttons[i].posY + (menuButtonSize / 4 * this.radialMenuScale) *
Math.sin(this.level1Buttons[i].angle + Math.PI / 2));
this.ctx.strokeStyle = "#ffffff";
this.ctx.lineWidth = 5 * this.radialMenuScale;
this.ctx.stroke();
}
}
/*
for (i = 0; i < this.level2Buttons.length; i++) {
if (this.level2Buttons[i].isHidden() === false) {
this.ctx.beginPath();
// We are adding -Math.PI/2 since angle also accounts for the initial orientation of the button image
var centerButton = this.level1Buttons[i];
this.ctx.moveTo(centerButton.posX + (menuButtonSize / 4 * this.radialMenuScale) *
Math.cos(this.level2Buttons[i].angle - Math.PI / 2),
centerButton.posY + (menuButtonSize / 4 * this.radialMenuScale) *
Math.sin(this.level2Buttons[i].angle - Math.PI / 2));
this.ctx.lineTo(this.level2Buttons[i].posX + (menuButtonSize / 4 * this.radialMenuScale) *
Math.cos(this.level2Buttons[i].angle + Math.PI / 2),
this.level2Buttons[i].posY + (menuButtonSize / 4 * this.radialMenuScale) *
Math.sin(this.level2Buttons[i].angle + Math.PI / 2));
this.ctx.strokeStyle = "#ffffff";
this.ctx.lineWidth = 5 * this.radialMenuScale;
this.ctx.stroke();
}
}
*/
if (this.level0Buttons.length === 0) {
this.radialCenterButton.draw();
}
if (this.currentRadialState === "radialMenu") {
for (i = 0; i < this.level0Buttons.length; i++) {
this.level0Buttons[i].draw();
}
for (i = 0; i < this.level1Buttons.length; i++) {
this.level1Buttons[i].draw();
}
if (this.showArrangementSubmenu) {
for (i = 0; i < this.level2Buttons.length; i++) {
this.level2Buttons[i].draw();
}
}
}
this.drawThumbnailWindow();
this.ctx.redraw = false;
};
this.drawThumbnailWindow = function() {
var i;
if (this.thumbScrollWindowctx.redraw || this.currentMenuState === "radialMenu") {
this.thumbScrollWindowctx.clearRect(0, 0,
this.thumbnailScrollWindowElement.width,
this.thumbnailScrollWindowElement.height);
}
if (this.windowInteractionMode === false) {
this.ctx.fillStyle = "rgba(5, 15, 55, 0.5)";
this.thumbScrollWindowctx.fillStyle = this.ctx.fillStyle;
} else if (this.dragThumbnailWindow === true) {
this.ctx.fillStyle = "rgba(55, 55, 5, 0.5)";
this.thumbScrollWindowctx.fillStyle = this.ctx.fillStyle;
} else {
this.ctx.fillStyle = "rgba(5, 5, 5, 0.5)";
this.thumbScrollWindowctx.fillStyle = this.ctx.fillStyle;
}
// Thumbnail window
if (this.currentMenuState !== "radialMenu") {
this.thumbnailWindowDiv.style.backgroundColor = "rgba(5,5,5,0.5)";
var currentThumbnailButtons = this.imageThumbnailButtons;
if (this.currentMenuState === "imageThumbnailWindow") {
currentThumbnailButtons = this.imageThumbnailButtons;
} else if (this.currentMenuState === "pdfThumbnailWindow") {
currentThumbnailButtons = this.pdfThumbnailButtons;
} else if (this.currentMenuState === "videoThumbnailWindow") {
currentThumbnailButtons = this.videoThumbnailButtons;
} else if (this.currentMenuState === "applauncherThumbnailWindow") {
currentThumbnailButtons = this.appThumbnailButtons;
} else if (this.currentMenuState === "sessionThumbnailWindow") {
currentThumbnailButtons = this.sessionThumbnailButtons;
}
if (this.thumbScrollWindowctx.redraw) {
for (i = 0; i < currentThumbnailButtons.length; i++) {
var thumbButton = currentThumbnailButtons[i];
thumbButton.draw();
}
this.thumbScrollWindowctx.redraw = false;
}
// Preview window
var previewImageSize = this.element.width * previewWindowWidth;
var previewImageX = this.thumbnailWindowSize.x + this.imageThumbSize / 2 - 10;
var previewImageY = 60 + this.textHeaderHeight;
// Metadata
var metadataLine = 0;
var metadataTextPosX = previewImageX;
var metadataTextPosY = previewImageY + previewImageSize + 20 + this.textHeaderHeight;
// Preview Window Background
if (this.currentMenuState !== "radialMenu") {
this.ctx.fillStyle = "rgba(5, 5, 5, 0.5)";
this.ctx.fillRect(previewImageX - 10, this.thumbnailWindowPosition.y + this.textHeaderHeight,
previewImageSize + 20, this.thumbnailWindowSize.y);
}
this.borderLineThickness = 5 * this.radialMenuScale;
// Thumbnail window - Title bar
this.ctx.beginPath();
this.ctx.moveTo(this.thumbnailWindowPosition.x - 18 * this.radialMenuScale - this.borderLineThickness / 2,
this.borderLineThickness / 2);
// Top vertical line
this.ctx.lineTo(previewImageX - 10 - 40 * this.radialMenuScale + 2.5 * this.radialMenuScale -
this.borderLineThickness / 2, this.borderLineThickness / 2);
// Angled line
this.ctx.lineTo(previewImageX - 10 - this.borderLineThickness, this.thumbnailWindowPosition.y +
this.textHeaderHeight - this.borderLineThickness / 2);
// Bottom horizontal line
this.ctx.lineTo(this.thumbnailWindowPosition.x - 18 * this.radialMenuScale - this.borderLineThickness / 2,
this.thumbnailWindowPosition.y + this.textHeaderHeight - this.borderLineThickness / 2);
this.ctx.closePath();
this.ctx.fillStyle = "#50505080";
this.ctx.fill();
this.ctx.strokeStyle = "#ffffff";
this.ctx.lineWidth = 5 * this.radialMenuScale;
this.ctx.stroke();
// Thumbnail window - Vert line
this.ctx.beginPath();
this.ctx.moveTo(this.thumbnailWindowPosition.x - 18 * this.radialMenuScale - this.borderLineThickness / 2,
this.thumbnailWindowPosition.y + this.textHeaderHeight);
this.ctx.lineTo(this.thumbnailWindowPosition.x - 18 * this.radialMenuScale - this.borderLineThickness / 2,
this.thumbnailWindowSize.y);
this.ctx.strokeStyle = "#ffffff";
this.ctx.lineWidth = 5 * this.radialMenuScale;
this.ctx.stroke();
// Thumbnail window - Horz line across preview window
this.ctx.beginPath();
this.ctx.moveTo(previewImageX - 10 - 5 * this.radialMenuScale, this.thumbnailWindowPosition.y +
this.textHeaderHeight - this.borderLineThickness / 2);
this.ctx.lineTo(previewImageX - 10 + previewImageSize + 20, this.thumbnailWindowPosition.y +
this.textHeaderHeight - this.borderLineThickness / 2);
this.ctx.strokeStyle = "#ffffff";
this.ctx.lineWidth = 5 * this.radialMenuScale;
this.ctx.stroke();
// Filename text
this.ctx.font = parseInt(this.textHeaderHeight) + "px sans-serif";
this.ctx.fillStyle = "rgba(250, 250, 250, 1.0)";
this.ctx.fillText(this.hoverOverText, this.thumbnailWindowPosition.x,
this.thumbnailWindowPosition.y + this.textHeaderHeight / 1.8);
if (this.hoverOverThumbnail) {
this.ctx.drawImage(this.hoverOverThumbnail, previewImageX, previewImageY,
previewImageSize, previewImageSize);
}
if (this.hoverOverMeta) {
this.ctx.font = "16px sans-serif";
this.ctx.fillStyle = "rgba(250, 250, 250, 1.0)";
var metadata = this.hoverOverMeta;
var metadataTags = [];
// Generic
metadataTags[0] = { tag: metadata.FileName, longLabel: "File Name: " };
if (metadata.FileSize !== undefined) {
metadataTags[1] = { tag: this.bytesToReadableString(metadata.FileSize), longLabel: "File Size: " };
}
metadataTags[2] = { tag: metadata.FileDate, longLabel: "File Date: " };
// Image
metadataTags[3] = { tag: metadata.ImageSize, longLabel: "Resolution: " };
metadataTags[4] = { tag: metadata.DateCreated, longLabel: "Date Created: " };
metadataTags[5] = { tag: metadata.Copyright, longLabel: "Copyright: " };
// Photo
metadataTags[6] = { tag: metadata.Artist, longLabel: "Artist: " };
metadataTags[7] = { tag: metadata.Aperture, longLabel: "Aperture: " };
metadataTags[8] = { tag: metadata.Exposure, longLabel: "Exposure: " };
metadataTags[9] = { tag: metadata.Flash, longLabel: "Flash: " };
metadataTags[10] = { tag: metadata.ExposureTime, longLabel: "Exposure Time: " };
metadataTags[11] = { tag: metadata.FOV, longLabel: "FOV: " };
metadataTags[12] = { tag: metadata.FocalLength, longLabel: "Focal Length: " };
metadataTags[13] = { tag: metadata.Model, longLabel: "Model: " };
metadataTags[14] = { tag: metadata.LensModel, longLabel: "Lens Model: " };
metadataTags[15] = { tag: metadata.ISO, longLabel: "ISO: " };
metadataTags[16] = { tag: metadata.ShutterSpeed, longLabel: "Shutter Speed: " };
// GPS
metadataTags[17] = { tag: metadata.GPSAltitude, longLabel: "GPS Altitude: " };
metadataTags[18] = { tag: metadata.GPSLatitude, longLabel: "GPS Latitude: " };
metadataTags[19] = { tag: metadata.GPSTimeStamp, longLabel: "GPS TimeStamp: " };
// Video
metadataTags[20] = { tag: metadata.Duration, longLabel: "Duration: " };
metadataTags[21] = { tag: metadata.CompressorID, longLabel: "Compressor: " };
metadataTags[22] = { tag: metadata.AvgBitrate, longLabel: "Avg. Bitrate: " };
metadataTags[23] = { tag: metadata.AudioFormat, longLabel: "Audio Format: " };
metadataTags[24] = { tag: metadata.AudioChannels, longLabel: "Audio Channels: " };
metadataTags[25] = { tag: metadata.AudioSampleRate, longLabel: "Audio Sample Rate: " };
// Apps
if (metadata.metadata !== undefined) {
metadataTags[26] = { tag: metadata.metadata.title, longLabel: "Title: " };
metadataTags[27] = { tag: metadata.metadata.version, longLabel: "Version: " };
metadataTags[28] = { tag: metadata.metadata.author, longLabel: "Author: " };
metadataTags[29] = { tag: metadata.metadata.license, longLabel: "License: " };
metadataTags[30] = { tag: metadata.metadata.keywords, longLabel: "Keywords: " };
metadataTags[31] = { tag: metadata.metadata.description, longLabel: "Description: " };
}
// Sessions
metadataTags[32] = { tag: metadata.numapps, longLabel: "Applications: " };
var newTagSpacing = 28;
var sameTagSpacing = 20;
/*
// No word wrap (Debugging purposes only now)
for (i = 0; i < metadataTags.length; i++) {
if (metadataTags[i] !== undefined && metadataTags[i].tag) {
this.ctx.fillText(metadataTags[i].longLabel + metadataTags[i].tag,
metadataTextPosX, metadataTextPosY + metadataLine * newTagSpacing);
metadataLine++;
}
}
*/
// Word Wrap
for (i = 0; i < metadataTags.length; i++) {
if (metadataTags[i] !== undefined && metadataTags[i].tag) {
var labelLength = this.ctx.measureText(metadataTags[i].longLabel).width;
var tagLength = this.ctx.measureText(metadataTags[i].tag).width;
var maxTextWidth = this.element.width * previewWindowWidth;
if (labelLength + tagLength <= maxTextWidth) {
this.ctx.fillText(metadataTags[i].longLabel + metadataTags[i].tag,
metadataTextPosX, metadataTextPosY + metadataLine * newTagSpacing);
} else {
var textWords = (metadataTags[i].longLabel + metadataTags[i].tag).split(' ');
var testLine = "";
var j = 0;
var line = 0;
for (j = 0; j < textWords.length; j++) {
var nextTestLine = testLine + textWords[j] + " ";
if (this.ctx.measureText(nextTestLine).width <= maxTextWidth) {
testLine = nextTestLine;
} else {
this.ctx.fillText(testLine,
metadataTextPosX,
metadataTextPosY + metadataLine * newTagSpacing + sameTagSpacing * line);
testLine = textWords[j] + " ";
line += 1;
}
}
this.ctx.fillText(testLine,
metadataTextPosX,
metadataTextPosY + metadataLine * newTagSpacing + sameTagSpacing * line);
if (line > 0) {
metadataLine++;
}
}
metadataLine++;
}
}
}
}
};
/**
* Converts bytes to human readable string
*
* @method bytesToReadableString
*/
this.bytesToReadableString = function(bytes) {
var bytesInt = parseInt(bytes);
if (bytesInt > Math.pow(1024, 3)) {
return (bytesInt / Math.pow(1024, 3)).toFixed(2) + " GB";
}
if (bytesInt > Math.pow(1024, 2)) {
return (bytesInt / Math.pow(1024, 2)).toFixed(2) + " MB";
}
if (bytesInt > Math.pow(1024, 1)) {
return Math.round(bytesInt / Math.pow(1024, 1)) + " KB";
}
return bytes + " bytes";
};
/**
* Closes the menu, sends close signal to server
*
* @method closeMenu
*/
this.closeMenu = function() {
// console.log("radialMenu: closeMenu");
this.visible = false;
this.radialMenuDiv.style.display = "none";
this.thumbnailWindowDiv.style.display = "none";
this.currentMenuState = "radialMenu";
};
/**
* Toggles the content window open/close
*
* @method setToggleMenu
*/
this.setToggleMenu = function(type) {
// console.log("radialMenu: setToggleMenu " + type);
if (this.currentMenuState !== type) {
this.thumbnailWindowScrollOffset = { x: 0, y: 0 };
this.currentMenuState = type;
this.element.width = this.thumbnailWindowSize.x + thumbnailPreviewWindowSize.x;
this.element.height = this.thumbnailWindowSize.y;
this.thumbnailScrollWindowElement.style.display = "block";
this.thumbnailWindowDiv.style.display = "block";
this.thumbScrollWindowctx.redraw = true;
this.updateThumbnailPositions();
this.draw();
return true;
}
this.currentMenuState = "radialMenu";
this.element.width = this.radialMenuSize.x;
this.element.height = this.radialMenuSize.y;
this.thumbnailWindowDiv.style.display = "none";
this.draw();
return false;
};
/**
* Sets the content window
*
* @method setMenu
*/
this.setMenu = function(type) {
if (type !== "radialMenu") {
// console.log("radialMenu: setMenu " + type);
this.thumbnailWindowScrollOffset = { x: 0, y: 0 };
this.currentMenuState = type;
this.element.width = this.thumbnailWindowSize.x + thumbnailPreviewWindowSize.x;
this.element.height = this.thumbnailWindowSize.y;
this.thumbnailScrollWindowElement.style.display = "block";
this.thumbnailWindowDiv.style.display = "block";
this.thumbScrollWindowctx.redraw = true;
this.updateThumbnailPositions();
this.draw();
} else {
// console.log("radialMenu: setMenu " + type);
this.currentMenuState = "radialMenu";
this.element.width = this.radialMenuSize.x;
this.element.height = this.radialMenuSize.y;
this.thumbnailWindowDiv.style.display = "none";
this.draw();
}
};
/**
* Toggles a subradial menu
*
* @method toggleSubRadialMenu
*/
this.toggleSubRadialMenu = function(type) {
this.showArrangementSubmenu = !this.showArrangementSubmenu;
// console.log("radialMenu: toggleSubRadialMenu - " + this.showArrangementSubmenu);
};
/**
* Moves the radial menu based on master and server events
*
* @method moveMenu
* @param data {x: data.x, y: data.y, windowX: rect.left, windowY: rect.top}
* Contains the event position and the bounding rectangle
* @param offset {x: this.offsetX, y: this.offsetY} Contains the display client offset
*/
this.moveMenu = function(data, offset) {
// Note: We don"t check if the pointer is over the menu because the server/node-radialMenu does this for us
if (this.windowInteractionMode === false && this.buttonOverCount === 0) {
var dragOffset = this.dragPosition;
this.element.style.left = (data.x - offset.x - dragOffset.x).toString() + "px";
this.element.style.top = (data.y - offset.y - dragOffset.y).toString() + "px";
}
this.thumbnailWindowDiv.style.left = (data.windowX + this.thumbnailWindowPosition.x -
18 * this.radialMenuScale).toString() + "px";
this.thumbnailWindowDiv.style.top = (data.windowY + this.thumbnailWindowPosition.y +
this.textHeaderHeight).toString() + "px";
this.thumbnailWindowDiv.style.width = (this.thumbnailWindowSize.x + this.imageThumbSize / 2 -
10 - this.radialMenuSize.x - 25 * this.radialMenuScale).toString() + "px";
this.thumbnailWindowDiv.style.height = (this.thumbnailWindowSize.y -
this.textHeaderHeight * 2).toString() + "px";
};
/**
* Processes events
*
* @method onEvent
* @param type {String} i.e. "pointerPress", "pointerMove", "pointerRelease"
* @param position {x: Float, y: Float} event position
* @param user {Integer} userID
* @param data { button: "left/right", color: "#000000" }
*/
this.onEvent = function(type, position, user, data) {
this.buttonOverCount = 0; // Count number of buttons have a pointer over it
// Level 1 -----------------------------------
var i = 0;
if (this.currentRadialState === "radialMenu") {
for (i = 0; i < this.level1Buttons.length; i++) {
this.buttonOverCount += this.level1Buttons[i].onEvent(type, user.id, position, data);
}
}
// Thumbnail window ----------------------------
if (this.currentMenuState !== "radialMenu") {
var currentThumbnailButtons = this.imageThumbnailButtons;
if (this.currentMenuState === "imageThumbnailWindow") {
currentThumbnailButtons = this.imageThumbnailButtons;
} else if (this.currentMenuState === "pdfThumbnailWindow") {
currentThumbnailButtons = this.pdfThumbnailButtons;
} else if (this.currentMenuState === "videoThumbnailWindow") {
currentThumbnailButtons = this.videoThumbnailButtons;
} else if (this.currentMenuState === "applauncherThumbnailWindow") {
currentThumbnailButtons = this.appThumbnailButtons;
} else if (this.currentMenuState === "sessionThumbnailWindow") {
currentThumbnailButtons = this.sessionThumbnailButtons;
}
var thumbUpdated = false;
for (i = 0; i < currentThumbnailButtons.length; i++) {
var thumbButton = currentThumbnailButtons[i];
var thumbEventPos = {
x: position.x - this.thumbnailWindowPosition.x + 18 * this.radialMenuScale,
y: position.y - this.thumbnailWindowPosition.y - this.textHeaderHeight
};
// Prevent clicking on hidden thumbnails under preview window
// should match where "this.thumbnailWindowDiv.style.width" is assigned
var thumbnailWindowDivWidth = this.thumbnailWindowSize.x + this.imageThumbSize / 2 - 10 -
this.radialMenuSize.x - 25 * this.radialMenuScale;
if (thumbEventPos.x >= 0 && thumbEventPos.x <= thumbnailWindowDivWidth) {
thumbEventPos.x -= this.thumbnailWindowScrollOffset.x;
this.buttonOverCount += thumbButton.onEvent(type, user.id, thumbEventPos, data);
if (thumbButton.isReleased() && this.scrollOpenContentLock === false && data.button === "left") {
if (this.currentMenuState === "applauncherThumbnailWindow") {
this.loadApplication(thumbButton.getData(), user);
} else {
this.loadFileFromServer(thumbButton.getData(), user);
}
}
if (thumbButton.isPositionOver(user.id, thumbEventPos)) {
this.hoverOverText = thumbButton.getData().shortname;
this.hoverOverThumbnail = thumbButton.buttonImage;
if (thumbButton.buttonImage.lsrc) {
this.hoverOverThumbnail.src = thumbButton.buttonImage.lsrc;
}
this.hoverOverMeta = thumbButton.getData().meta;
}
// Only occurs on first pointerMove event over button
if (thumbButton.isFirstOver()) {
thumbUpdated = true;
}
}
}
if (thumbUpdated) {
this.redraw();
}
}
// windowInteractionMode = true if any active button has an event over its
if (type === "pointerPress" && data.button === "left") {
// Press over radial menu, drag menu
if (position.x > 0 && position.x < this.radialMenuSize.x && position.y > 0 &&
position.y < this.radialMenuSize.y && this.buttonOverCount === 0) {
this.windowInteractionMode = false;
this.dragPosition = position;
}
if (position.x > this.radialMenuSize.x && position.x < this.thumbnailWindowSize.x &&
position.y > 0 && position.y < this.thumbnailWindowSize.y) {
if (this.dragThumbnailWindow === false) {
this.dragThumbnailWindow = true;
this.thumbnailWindowDragPosition = position;
this.thumbnailWindowInitialScrollOffset.x = this.thumbnailWindowScrollOffset.x;
this.thumbnailWindowInitialScrollOffset.y = this.thumbnailWindowScrollOffset.y;
}
}
this.ctx.redraw = true;
this.scrollOpenContentLock = false;
} else if (type === "pointerMove") {
if (this.dragThumbnailWindow === true) {
// Controls the content window scrolling.
// Note:Scrolling is +right, -left so offset should always be negative
if (this.thumbnailWindowScrollOffset.x <= 0 && this.notEnoughThumbnailsToScroll === false) {
var nextScrollPos = this.thumbnailWindowScrollOffset;
nextScrollPos.x += (position.x - this.thumbnailWindowDragPosition.x) * thumbnailScrollScale;
nextScrollPos.y += (position.y - this.thumbnailWindowDragPosition.y) * thumbnailScrollScale;
if (nextScrollPos.x > 0) {
nextScrollPos.x = 0;
}
if (nextScrollPos.y > 0) {
nextScrollPos.y = 0;
}
if (-nextScrollPos.x < this.maxThumbnailScrollDistance) {
this.scrollThumbnailWindow(nextScrollPos);
this.thumbnailWindowDragPosition = position;
} else {
nextScrollPos.x = -this.maxThumbnailScrollDistance;
this.scrollThumbnailWindow(nextScrollPos);
this.thumbnailWindowDragPosition = position;
}
} else {
this.thumbnailWindowScrollOffset.x = 0;
}
}
} else if (type === "pointerRelease") {
if (this.windowInteractionMode === false) {
this.windowInteractionMode = true;
this.dragPosition = { x: 0, y: 0 };
} else if (this.dragThumbnailWindow === true) {
this.dragThumbnailWindow = false;
}
} else if (type === "pointerScroll") {
if (this.thumbnailWindowScrollOffset.x <= 0 && this.notEnoughThumbnailsToScroll === false) {
var wheelDelta = this.thumbnailWindowScrollOffset.x + data.wheelDelta;
if (-wheelDelta < this.maxThumbnailScrollDistance) {
this.scrollThumbnailWindow({x: wheelDelta, y: 0 });
}
}
}
};
this.scrollThumbnailWindow = function(nextScrollPos) {
var scrollDist = 0;
if (this.thumbnailWindowScrollLock.x === false) {
this.thumbnailWindowScrollOffset.x = nextScrollPos.x;
scrollDist += this.thumbnailWindowInitialScrollOffset.x - this.thumbnailWindowScrollOffset.x;
}
if (this.thumbnailWindowScrollLock.y === false) {
this.thumbnailWindowScrollOffset.y = nextScrollPos.y;
scrollDist += this.thumbnailWindowInitialScrollOffset.y - this.thumbnailWindowScrollOffset.y;
}
if (scrollDist < 0) {
scrollDist *= -1;
}
if (scrollDist >= thumbnailDisableSelectionScrollDistance) {
this.scrollOpenContentLock = true;
}
if (this.thumbnailWindowScrollOffset.x > 0) {
this.thumbnailWindowScrollOffset.x = 0;
}
this.thumbnailScrollWindowElement.style.left = (this.thumbnailWindowScrollOffset.x).toString() + "px";
};
/**
* Tells the server to load a file
*
* @method loadFileFromServer
* @param data {} Content information like type and filename
* @param user {Integer} userID
*/
this.loadFileFromServer = function(data, user) {
if (this.sendsToServer === true) {
this.wsio.emit("loadFileFromServer", { application: data.application, filename: data.filename, user: user});
}
};
/**
* Tells the server to start an application
*
* @method loadApplication
* @param data {} Application information like filename
* @param user {Integer} userID
*/
this.loadApplication = function(data, user) {
if (this.sendsToServer === true) {
this.wsio.emit("loadApplication", { application: data.filename, user: user});
}
};
/**
* Receives the current asset list from server
*
* @method updateFileList
* @param serverFileList {} Server file list
*/
this.updateFileList = function(serverFileList) {
this.thumbnailButtons = [];
this.imageThumbnailButtons = [];
this.videoThumbnailButtons = [];
this.pdfThumbnailButtons = [];
this.appThumbnailButtons = [];
this.sessionThumbnailButtons = [];
// Server file lists by type
var imageList = serverFileList.images;
var pdfList = serverFileList.pdfs;
var videoList = serverFileList.videos;
var appList = serverFileList.applications;
var sessionList = serverFileList.sessions;
var i = 0;
var thumbnailButton;
var customIcon;
var invalidFilenameRegex = "[:#$%^&@]";
var data;
var curList;
if (imageList !== null) {
// var validImages = 0;
for (i = 0; i < imageList.length; i++) {
if (imageList[i].filename.search("Thumbs.db") === -1) {
thumbnailButton = new ButtonWidget();
thumbnailButton.init(0, this.thumbScrollWindowctx, null);
curList = imageList[i];
data = {
application: "image_viewer",
filename: curList.filename,
shortname: curList.exif.FileName,
meta: curList.exif
};
thumbnailButton.setData(data);
thumbnailButton.simpleTint = false;
thumbnailButton.setSize(this.imageThumbSize, this.imageThumbSize);
thumbnailButton.setHitboxSize(this.imageThumbSize, this.imageThumbSize);
// Thumbnail image
if (imageList[i].exif.SAGE2thumbnail !== null) {
customIcon = new Image();
customIcon.lsrc = imageList[i].exif.SAGE2thumbnail + "_512.jpg";
customIcon.src = imageList[i].exif.SAGE2thumbnail + "_256.jpg";
thumbnailButton.setButtonImage(customIcon);
thumbnailButton.setDefaultImage(radialMenuIcons["images/ui/images.svg"]);
} else {
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/images.svg"]);
}
// File has a bad filename for thumbnails, set default icon
if (imageList[i].exif.SAGE2thumbnail !== undefined &&
imageList[i].exif.SAGE2thumbnail.match(invalidFilenameRegex) !== null) {
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/images.svg"]);
}
this.thumbnailButtons.push(thumbnailButton);
this.imageThumbnailButtons.push(thumbnailButton);
// validImages++;
}
}
}
if (pdfList !== null) {
for (i = 0; i < pdfList.length; i++) {
thumbnailButton = new ButtonWidget();
thumbnailButton.init(0, this.thumbScrollWindowctx, null);
curList = pdfList[i];
data = {
application: "pdf_viewer",
filename: curList.filename,
shortname: curList.exif.FileName,
meta: curList.exif
};
thumbnailButton.setData(data);
thumbnailButton.simpleTint = false;
thumbnailButton.setSize(this.imageThumbSize, this.imageThumbSize);
thumbnailButton.setHitboxSize(this.imageThumbSize, this.imageThumbSize);
// Thumbnail image
if (pdfList[i].exif.SAGE2thumbnail !== null) {
customIcon = new Image();
customIcon.lsrc = pdfList[i].exif.SAGE2thumbnail + "_512.jpg";
customIcon.src = pdfList[i].exif.SAGE2thumbnail + "_256.jpg";
thumbnailButton.setButtonImage(customIcon);
thumbnailButton.setDefaultImage(radialMenuIcons["images/ui/pdfs.svg"]);
} else {
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/pdfs.svg"]);
}
// File has a bad filename for thumbnails, set default icon
if (pdfList[i].exif.SAGE2thumbnail !== undefined
&& pdfList[i].exif.SAGE2thumbnail.match(invalidFilenameRegex) !== null) {
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/pdfs.svg"]);
}
this.thumbnailButtons.push(thumbnailButton);
this.pdfThumbnailButtons.push(thumbnailButton);
}
}
if (videoList !== null) {
for (i = 0; i < videoList.length; i++) {
thumbnailButton = new ButtonWidget();
thumbnailButton.init(0, this.thumbScrollWindowctx, null);
curList = videoList[i];
data = {
application: "movie_player",
filename: curList.filename,
shortname: curList.exif.FileName,
meta: curList.exif
};
thumbnailButton.setData(data);
thumbnailButton.simpleTint = false;
thumbnailButton.setSize(this.imageThumbSize, this.imageThumbSize);
thumbnailButton.setHitboxSize(this.imageThumbSize, this.imageThumbSize);
// Thumbnail image
if (videoList[i].exif.SAGE2thumbnail !== null) {
customIcon = new Image();
customIcon.lsrc = videoList[i].exif.SAGE2thumbnail + "_512.jpg";
customIcon.src = videoList[i].exif.SAGE2thumbnail + "_256.jpg";
thumbnailButton.setButtonImage(customIcon);
thumbnailButton.setDefaultImage(radialMenuIcons["images/ui/videos.svg"]);
} else {
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/videos.svg"]);
}
// File has a bad filename for thumbnails, set default icon
if (videoList[i].exif.SAGE2thumbnail !== undefined
&& videoList[i].exif.SAGE2thumbnail.match(invalidFilenameRegex) !== null) {
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/videos.svg"]);
}
this.thumbnailButtons.push(thumbnailButton);
this.videoThumbnailButtons.push(thumbnailButton);
}
}
if (appList !== null) {
for (i = 0; i < appList.length; i++) {
thumbnailButton = new ButtonWidget();
thumbnailButton.init(0, this.thumbScrollWindowctx, null);
data = {
application: "custom_app",
filename: appList[i].filename,
shortname: appList[i].exif.FileName,
meta: appList[i].exif
};
thumbnailButton.setData(data);
thumbnailButton.simpleTint = false;
thumbnailButton.useBackgroundColor = true;
thumbnailButton.setSize(this.imageThumbSize * 2, this.imageThumbSize * 2);
thumbnailButton.setHitboxSize(this.imageThumbSize * 2, this.imageThumbSize * 2);
if (appList[i].exif.SAGE2thumbnail !== null) {
customIcon = new Image();
customIcon.lsrc = appList[i].exif.SAGE2thumbnail + "_512.jpg";
customIcon.src = appList[i].exif.SAGE2thumbnail + "_256.jpg";
thumbnailButton.setButtonImage(customIcon);
thumbnailButton.setDefaultImage(radialMenuIcons["images/ui/applauncher.svg"]);
} else {
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/applauncher.svg"]);
}
// File has a bad filename for thumbnails, set default icon
if (appList[i].exif.SAGE2thumbnail !== undefined
&& appList[i].exif.SAGE2thumbnail.match(invalidFilenameRegex) !== null) {
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/applauncher.svg"]);
}
this.thumbnailButtons.push(thumbnailButton);
this.appThumbnailButtons.push(thumbnailButton);
}
}
if (sessionList !== null) {
for (i = 0; i < sessionList.length; i++) {
thumbnailButton = new ButtonWidget();
thumbnailButton.init(0, this.thumbScrollWindowctx, null);
curList = sessionList[i];
data = {application: "load_session", filename: curList.id, shortname: curList.exif.FileName, meta: curList.exif};
thumbnailButton.setData(data);
thumbnailButton.setButtonImage(radialMenuIcons["images/ui/loadsession.svg"]);
thumbnailButton.simpleTint = false;
thumbnailButton.setSize(this.imageThumbSize, this.imageThumbSize);
thumbnailButton.setHitboxSize(this.imageThumbSize, this.imageThumbSize);
this.thumbnailButtons.push(thumbnailButton);
this.sessionThumbnailButtons.push(thumbnailButton);
}
}
this.updateThumbnailPositions();
};
/**
* Helper function for arranging thumbnails
*
* @method setThumbnailPosition
* @param thumbnailSourceList {} List of thumbnails
* @param imageThumbnailSize {Float} width of thumbnail in pixels
* @param thumbSpacer {Float} space between thumbnails in pixels
* @param maxRows {Integer} maximum thumbnails per row
* @param neededColumns {Integer} calculated number of columns needed
*/
this.setThumbnailPosition = function(thumbnailSourceList, imageThumbnailSize, thumbnailSpacer, maxRows, neededColumns) {
var curRow = 0;
var curColumn = 0;
this.thumbnailScrollWindowElement.width = (imageThumbnailSize + thumbSpacer) * neededColumns;
for (var i = 0; i < thumbnailSourceList.length; i++) {
var currentButton = thumbnailSourceList[i];
if (curColumn + 1 > neededColumns) {
curColumn = 0;
if (curRow < maxRows - 1) {
curRow++;
}
}
currentButton.setPosition(curColumn * (imageThumbnailSize + thumbnailSpacer),
curRow * (imageThumbnailSize + thumbnailSpacer));
curColumn++;
}
};
/**
* Recalculates the thumbnail positions
*
* @method updateThumbnailPositions
*/
this.updateThumbnailPositions = function() {
var thumbWindowSize = this.thumbnailWindowSize;
// maxRows is considered a "hard" limit based on the thumbnail and window size.
// If maxRows and maxCols is exceeded, then maxCols is expanded as needed.
var maxRows = Math.floor((thumbWindowSize.y - this.thumbnailWindowPosition.y) / (this.imageThumbSize + thumbSpacer));
var maxCols = Math.floor((thumbWindowSize.x - this.thumbnailWindowPosition.x) / (this.imageThumbSize + thumbSpacer));
var neededColumns = maxRows;
if (this.currentMenuState === "imageThumbnailWindow") {
if (this.imageThumbnailButtons.length > (maxRows * maxCols)) {
neededColumns = Math.ceil(this.imageThumbnailButtons.length / maxRows);
}
} else if (this.currentMenuState === "pdfThumbnailWindow") {
if (this.pdfThumbnailButtons.length > (maxRows * maxCols)) {
neededColumns = Math.ceil(this.pdfThumbnailButtons.length / maxRows);
}
} else if (this.currentMenuState === "videoThumbnailWindow") {
if (this.videoThumbnailButtons.length > (maxRows * maxCols)) {
neededColumns = Math.ceil(this.videoThumbnailButtons.length / maxRows);
}
} else if (this.currentMenuState === "sessionThumbnailWindow") {
if (this.sessionThumbnailButtons.length > (maxRows * maxCols)) {
neededColumns = Math.ceil(this.sessionThumbnailButtons.length / maxRows);
}
}
this.maxThumbnailScrollDistance = (neededColumns - maxCols) * (this.imageThumbSize * 1 + thumbSpacer);
// Special thumbnail size for custom apps
if (this.currentMenuState === "applauncherThumbnailWindow") {
maxRows = Math.floor((thumbWindowSize.y - this.thumbnailWindowPosition.y) / (this.imageThumbSize * 2 + thumbSpacer));
maxCols = Math.floor((thumbWindowSize.x - this.thumbnailWindowPosition.x) / (this.imageThumbSize * 2 + thumbSpacer));
neededColumns = maxRows;
if (this.appThumbnailButtons.length > (maxRows * maxCols)) {
neededColumns = Math.ceil(this.appThumbnailButtons.length / maxRows);
}
this.maxThumbnailScrollDistance = (neededColumns - maxCols) * (this.imageThumbSize * 2 + thumbSpacer);
}
this.thumbnailGridSize = { x: maxRows, y: maxCols };
if (neededColumns > maxRows) {
this.notEnoughThumbnailsToScroll = false;
} else {
this.notEnoughThumbnailsToScroll = true;
this.thumbnailWindowScrollOffset.x = 0;
this.thumbnailScrollWindowElement.style.left = (this.thumbnailWindowScrollOffset.x).toString() + "px";
}
if (this.currentMenuState === "imageThumbnailWindow") {
this.setThumbnailPosition(this.imageThumbnailButtons, this.imageThumbSize, thumbSpacer, maxRows, neededColumns);
}
if (this.currentMenuState === "pdfThumbnailWindow") {
this.setThumbnailPosition(this.pdfThumbnailButtons, this.imageThumbSize, thumbSpacer, maxRows, neededColumns);
}
if (this.currentMenuState === "videoThumbnailWindow") {
this.setThumbnailPosition(this.videoThumbnailButtons, this.imageThumbSize, thumbSpacer, maxRows, neededColumns);
}
if (this.currentMenuState === "applauncherThumbnailWindow") {
this.setThumbnailPosition(this.appThumbnailButtons, this.imageThumbSize * 2, thumbSpacer, maxRows, neededColumns);
}
if (this.currentMenuState === "sessionThumbnailWindow") {
this.setThumbnailPosition(this.sessionThumbnailButtons, this.imageThumbSize, thumbSpacer, maxRows, neededColumns);
}
};
/**
* Sets the state of the radial menu: buttons, content windows
*
* @method setState
* @param stateData {} node-radialMenu.js getInfo()
*/
this.setState = function(stateData) {
// console.log("radialMenu: setState " + stateData.thumbnailWindowState);
// {id: this.pointerid, x: this.left, y: this.top, radialMenuSize: this.radialMenuSize,
// thumbnailWindowSize: this.thumbnailWindowSize, radialMenuScale: this.radialMenuScale,
// visble: this.visible, layout: this.radialButtons, thumbnailWindowState: this.thumbnailWindowState }
// console.log(stateData);
this.showArrangementSubmenu = stateData.arrangementMenuState;
if (stateData.thumbnailWindowState === "image") {
this.setMenu("imageThumbnailWindow");
} else if (stateData.thumbnailWindowState === "pdf") {
this.setMenu("pdfThumbnailWindow");
} else if (stateData.thumbnailWindowState === "video") {
this.setMenu("videoThumbnailWindow");
} else if (stateData.thumbnailWindowState === "applauncher") {
this.setMenu("applauncherThumbnailWindow");
} else if (stateData.thumbnailWindowState === "session") {
this.setMenu("sessionThumbnailWindow");
} else if (stateData.thumbnailWindowState === "closed") {
this.setMenu("radialMenu");
}
};
}
/**
* ButtonWidget used for menu and thumbnail buttons
*
* @class ButtonWidget
* @constructor
*/
function ButtonWidget() {
this.ctx = null;
this.resrcPath = null;
this.posX = 100;
this.posY = 100;
this.angle = 0;
this.width = imageThumbSize;
this.height = imageThumbSize;
this.hitboxWidth = imageThumbSize;
this.hitboxheight = imageThumbSize;
this.defaultColor = "rgba(210, 210, 210, 1.0)";
this.mouseOverColor = "rgba(210, 210, 10, 1.0)";
this.clickedColor = "rgba(10, 210, 10, 1.0)";
this.pressedColor = "rgba(10, 210, 210, 1.0)";
this.releasedColor = "rgba(10, 10, 210, 1.0)";
this.litColor = "rgba(10, 210, 210, 1.0)";
this.buttonImage = null;
this.overlayImage = null;
this.defaultImage = null;
this.useBackgroundColor = true;
this.useEventOverColor = false;
this.simpleTint = false;
this.alignment = "left";
this.hitboxShape = "box";
this.isLit = false;
this.isHoveredOver = false;
// Button states:
// -2 = Hidden (and Disabled)
// -1 = Disabled (Visible, but ignores events - eventually will be dimmed?)
// 0 = Idle
// 1 = First over
// 2 = Pressed
// 3 = Held
// 4 = Released
// 5 = Lit
// 6 = Over
this.state = 0;
this.buttonData = {};
this.init = function(id, ctx, resrc) {
this.ctx = ctx;
this.resrcPath = resrc;
this.tintImage = document.createElement("canvas");
this.tintImageCtx = this.tintImage.getContext("2d");
};
this.setPosition = function(x, y) {
this.posX = x;
this.posY = y;
};
this.setRotation = function(a) {
this.angle = a;
};
this.setData = function(data) {
this.buttonData = data;
};
this.setButtonImage = function(image) {
this.buttonImage = image;
};
this.setDefaultImage = function(image) {
this.defaultImage = image;
};
this.setOverlayImage = function(overlayImage, scale) {
this.overlayImage = overlayImage;
this.overlayScale = scale;
};
this.setSize = function(w, h) {
this.width = w;
this.height = h;
};
this.setHitboxSize = function(w, h) {
this.hitboxWidth = w;
this.hitboxheight = h;
};
this.getData = function() {
return this.buttonData;
};
this.draw = function() {
if (this.state === -2) { // Button is hidden
return;
}
// Default - align "left"
var translate = { x: this.posX, y: this.posY };
var offsetHitbox = { x: 0, y: 0 };
var offset = { x: 0, y: 0 };
if (this.alignment === "centered") {
offset = { x: -this.width / 2, y: -this.height / 2 };
offsetHitbox = { x: -this.hitboxWidth / 2, y: -this.hitboxWidth / 2 };
}
this.ctx.save();
this.ctx.translate(translate.x, translate.y);
if (this.useBackgroundColor) {
if (this.state === 5) {
this.ctx.fillStyle = this.litColor;
} else {
this.ctx.fillStyle = this.defaultColor;
}
if (this.hitboxShape === "box") {
this.ctx.fillRect(offsetHitbox.x, offsetHitbox.y, this.hitboxWidth, this.hitboxheight);
}
}
if (this.state === 1) {
this.ctx.fillStyle = this.mouseOverColor;
} else if (this.state === 3) {
this.ctx.fillStyle = this.clickedColor;
// this.state = 2; // Pressed state
} else if (this.state === 2) {
this.ctx.fillStyle = this.pressedColor;
} else if (this.state === 4) {
this.ctx.fillStyle = this.releasedColor;
// this.state = 1;
}
// Draw icon aligned centered
if (this.buttonImage !== null) {
// this.ctx.rotate( this.angle);
// draw the original image
try {
this.ctx.drawImage(this.buttonImage, offset.x, offset.y, this.width, this.height);
} catch (e) {
this.buttonImage = this.defaultImage;
this.ctx.drawImage(this.buttonImage, offset.x, offset.y, this.width, this.height);
}
if (this.state === 5) {
this.drawTintImage(this.buttonImage, offset, this.width, this.height, this.litColor, 0.5);
} else if (this.state === 1) {
this.drawTintImage(this.buttonImage, offset, this.width, this.height, this.mouseOverColor, 0.8);
}
}
this.ctx.restore();
if (this.overlayImage !== null) {
this.ctx.save();
this.ctx.translate(translate.x, translate.y);
this.ctx.drawImage(this.overlayImage, -this.width * this.overlayScale / 2,
-this.height * this.overlayScale / 2,
this.width * this.overlayScale,
this.height * this.overlayScale);
this.ctx.restore();
}
};
this.drawTintImage = function(image, offset, width, height, color, alpha) {
var im;
// Tint the image (Part 1)
// create offscreen buffer,
this.tintImage.width = width;
this.tintImage.height = height;
// Firefox doesnt seem to deal with blending SVG onto canvas
if (__SAGE2__.browser.isFirefox) {
// Render the SVG into an image
this.tintImageCtx.drawImage(image, 0, 0, width, height);
// and make an image
im = new Image();
im.src = this.tintImage.toDataURL();
}
// fill offscreen buffer with the tint color
this.tintImageCtx.fillStyle = color;
this.tintImageCtx.fillRect(0, 0, this.tintImage.width, this.tintImage.height);
// destination atop makes a result with an alpha channel identical to fg,
// but with all pixels retaining their original color *as far as I can tell*
this.tintImageCtx.globalCompositeOperation = "destination-in";
if (__SAGE2__.browser.isFirefox) {
this.tintImageCtx.drawImage(im, 0, 0, width, height);
im = null;
} else {
this.tintImageCtx.drawImage(image, 0, 0, width, height);
}
// then set the global alpha to the amound that you want to tint it,
// and draw the buffer directly on top of it.
this.ctx.globalAlpha = alpha;
// draw the tinted overlay
this.ctx.drawImage(this.tintImage, offset.x, offset.y, width, height);
};
this.onEvent = function(type, user, position, data) {
if (this.state < 0) {
// Button is disabled or hidden
return 0;
}
if (this.isPositionOver(user, position)) {
this.mouseOverColor = data.color;
if (type === "pointerPress" && this.state !== 2) {
this.state = 3; // Click state
if (this.useEventOverColor) {
this.ctx.redraw = true;
}
} else if (type === "pointerRelease") {
this.state = 4;
if (this.useEventOverColor) {
this.ctx.redraw = true;
}
} else if (type === "pointerMove") {
if (this.state !== 1) {
this.state = 1;
if (this.useEventOverColor) {
this.ctx.redraw = true;
}
} else if (this.state === 1) {
// this.state = 6;
}
}
/*else if (this.state !== 2) {
if (this.state !== 1) {
this.state = 5;
if (this.useEventOverColor) {
this.ctx.redraw = true;
}
}
else {
this.state = 1;
}
}*/
return 1;
}
if (this.isLit === false) {
if (this.state !== 0 && this.useEventOverColor) {
this.ctx.redraw = true;
}
this.state = 0;
}
return 0;
};
this.setButtonState = function(state) {
this.state = state;
this.ctx.redraw = true;
};
this.isPositionOver = function(id, position) {
var x = position.x;
var y = position.y;
if (this.alignment === "centered" && this.hitboxShape === "box") {
x += this.hitboxWidth / 2;
y += this.hitboxheight / 2;
}
if (this.hitboxShape === "box") {
if (x >= this.posX && x <= this.posX + this.hitboxWidth && y >= this.posY && y <= this.posY + this.hitboxheight) {
return true;
}
return false;
}
if (this.hitboxShape === "circle") {
var distance = Math.sqrt(Math.pow(Math.abs(x - this.posX), 2) + Math.pow(Math.abs(y - this.posY), 2));
if (distance <= this.hitboxWidth / 2) {
return true;
}
return false;
}
};
this.isFirstOver = function() {
if (this.state === 1) {
return true;
}
return false;
};
this.isOver = function() {
if (this.state === 1 || this.state === 6) {
return true;
}
return false;
};
this.isClicked = function() {
if (this.state === 3) {
this.state = 2;
return true;
}
return false;
};
this.isReleased = function() {
if (this.state === 4) {
this.state = 0;
return true;
}
return false;
};
this.isHidden = function() {
if (this.state === -2) {
return true;
}
return false;
};
this.isDisabled = function() {
if (this.state === -1) {
return true;
}
return false;
};
this.setHidden = function(val) {
if (val) {
this.state = -2;
} else {
this.state = 0;
}
};
this.setDisabled = function(val) {
if (val) {
this.state = -1;
} else {
this.state = 0;
}
};
}