// Copyright (c) 2016-17 Walter Bender // // This program is free software; you can redistribute it and/or // modify it under the terms of the The GNU Affero General Public // License as published by the Free Software Foundation; either // version 3 of the License, or (at your option) any later version. // // You should have received a copy of the GNU Affero General Public // License along with this library; if not, write to the Free Software // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA // This widget makes displays the status of selected parameters and // notes as they are being played. function StatusMatrix() { const BUTTONDIVWIDTH = 128; const BUTTONSIZE = 53; const ICONSIZE = 32; const OUTERWINDOWWIDTH = 620; const INNERWINDOWWIDTH = OUTERWINDOWWIDTH - BUTTONSIZE * 1.5; docById('statusDiv').style.visibility = 'hidden'; this.init = function(logo) { // Initializes the status matrix. First removes the // previous matrix and them make another one in DOM (document // object model) this._logo = logo; var w = window.innerWidth; this._cellScale = w / 1200; var iconSize = ICONSIZE * this._cellScale; var canvas = docById('myCanvas'); // Position the widget and make it visible. var statusDiv = docById('statusDiv'); statusDiv.style.visibility = 'visible'; statusDiv.setAttribute('draggable', 'true'); statusDiv.style.left = '200px'; statusDiv.style.top = '150px'; // The status buttons var statusButtonsDiv = docById('statusButtonsDiv'); statusButtonsDiv.style.display = 'inline'; statusButtonsDiv.style.visibility = 'visible'; statusButtonsDiv.style.width = BUTTONDIVWIDTH; statusButtonsDiv.innerHTML = '
'; var buttonTable = docById('statusButtonTable'); var header = buttonTable.createTHead(); var row = header.insertRow(0); // For the button callbacks var that = this; var cell = this._addButton(row, 'close-button.svg', ICONSIZE, _('close')); cell.onclick=function() { statusTableDiv.style.visibility = 'hidden'; statusButtonsDiv.style.visibility = 'hidden'; statusDiv.style.visibility = 'hidden'; } // We use this cell as a handle for dragging. var dragCell = this._addButton(row, 'grab.svg', ICONSIZE, _('drag')); dragCell.style.cursor = 'move'; this._dx = dragCell.getBoundingClientRect().left - statusDiv.getBoundingClientRect().left; this._dy = dragCell.getBoundingClientRect().top - statusDiv.getBoundingClientRect().top; this._dragging = false; this._target = false; this._dragCellHTML = dragCell.innerHTML; dragCell.onmouseover = function(e) { // In order to prevent the dragged item from triggering a // browser reload in Firefox, we empty the cell contents // before dragging. dragCell.innerHTML = ''; }; dragCell.onmouseout = function(e) { if (!that._dragging) { dragCell.innerHTML = that._dragCellHTML; } }; canvas.ondragover = function(e) { e.preventDefault(); }; canvas.ondrop = function(e) { if (that._dragging) { that._dragging = false; var x = e.clientX - that._dx; statusDiv.style.left = x + 'px'; var y = e.clientY - that._dy; statusDiv.style.top = y + 'px'; dragCell.innerHTML = that._dragCellHTML; } }; statusDiv.ondragover = function(e) { e.preventDefault(); }; statusDiv.ondrop = function(e) { if (that._dragging) { that._dragging = false; var x = e.clientX - that._dx; statusDiv.style.left = x + 'px'; var y = e.clientY - that._dy; statusDiv.style.top = y + 'px'; dragCell.innerHTML = that._dragCellHTML; } }; statusDiv.onmousedown = function(e) { that._dragging = true; that._target = e.target; }; statusDiv.ondragstart = function(e) { if (dragCell.contains(that._target)) { e.dataTransfer.setData('text/plain', ''); } else { e.preventDefault(); } }; // The status table var statusTableDiv = docById('statusTableDiv'); statusTableDiv.style.display = 'inline'; statusTableDiv.style.visibility = 'visible'; statusTableDiv.style.border = '0px'; // We use an outer div to scroll vertically and an inner div to // scroll horizontally. statusTableDiv.innerHTML = '
'; var n = Math.max(Math.floor((window.innerHeight * 0.5) / 100), 8); var outerDiv = docById('statusOuterDiv'); if (this._logo.turtles.turtleList.length > n) { outerDiv.style.height = this._cellScale * MATRIXSOLFEHEIGHT * (n + 2) + 'px'; var w = Math.max(Math.min(window.innerWidth, this._cellScale * OUTERWINDOWWIDTH), BUTTONDIVWIDTH); outerDiv.style.width = w + 'px'; } else { if (this._logo.statusFields.length > 4) { // Assume we need a horizontal slider outerDiv.style.height = this._cellScale * (MATRIXBUTTONHEIGHT2 + (2 + MATRIXSOLFEHEIGHT) * this._logo.turtles.turtleList.length) + 30 + 'px'; } else { outerDiv.style.height = this._cellScale * (MATRIXBUTTONHEIGHT2 + (2 + MATRIXSOLFEHEIGHT) * this._logo.turtles.turtleList.length) + 'px'; } var w = Math.max(Math.min(window.innerWidth, this._cellScale * OUTERWINDOWWIDTH - 20), BUTTONDIVWIDTH); outerDiv.style.width = w + 'px'; } var w = Math.max(Math.min(window.innerWidth, this._cellScale * INNERWINDOWWIDTH), BUTTONDIVWIDTH - BUTTONSIZE); var innerDiv = docById('statusInnerDiv'); innerDiv.style.width = w + 'px'; innerDiv.style.marginLeft = (BUTTONSIZE * this._cellScale) + 'px'; // Each row in the status table contains a note label in the // first column and a table of buttons in the second column. var statusTable = docById('statusTable'); var header = statusTable.createTHead(); var row = header.insertRow(); var iconSize = Math.floor(this._cellScale * 24); var cell = row.insertCell(); cell.style.backgroundColor = MATRIXBUTTONCOLOR; cell.className = 'headcol'; cell.style.height = Math.floor(MATRIXBUTTONHEIGHT * this._cellScale) + 'px'; cell.style.width = (BUTTONSIZE * this._cellScale) + 'px'; cell.innerHTML = ' '; // One column per field for (var i = 0; i < this._logo.statusFields.length; i++) { var cell = row.insertCell(i + 1); cell.style.fontSize = Math.floor(this._cellScale * 100) + '%'; switch (this._logo.statusFields[i][1]) { case 'plus': case 'minus': case 'neg': case 'divide': case 'power': case 'multiply': case 'sqrt': case 'int': case 'mod': var label = ''; break; case 'namedbox': var label = this._logo.blocks.blockList[this._logo.statusFields[i][0]].privateData; break; default: var label = this._logo.blocks.blockList[this._logo.statusFields[i][0]].protoblock.staticLabels[0]; break; } cell.innerHTML = ' ' + label + ' ' cell.style.height = Math.floor(MATRIXBUTTONHEIGHT * this._cellScale) + 'px'; cell.style.backgroundColor = MATRIXBUTTONCOLOR; } if (_THIS_IS_MUSIC_BLOCKS_) { var cell = row.insertCell(); cell.style.fontSize = Math.floor(this._cellScale * 100) + '%'; cell.innerHTML = ' ' + _('note') + ' ' cell.style.height = Math.floor(MATRIXBUTTONHEIGHT * this._cellScale) + 'px'; cell.style.backgroundColor = MATRIXBUTTONCOLOR; } // One row per voice (turtle) var activeTurtles = 0; for (var turtle = 0; turtle < this._logo.turtles.turtleList.length; turtle++) { if (this._logo.turtles.turtleList[turtle].trash) { continue; } var row = header.insertRow(); var cell = row.insertCell(); cell.style.backgroundColor = MATRIXLABELCOLOR; if (_THIS_IS_MUSIC_BLOCKS_) { cell.innerHTML = '  ' + this._logo.turtles.turtleList[turtle].name + '  '; } else { cell.innerHTML = '  ' + this._logo.turtles.turtleList[turtle].name + '  '; } cell.style.width = (BUTTONSIZE * this._cellScale) + 'px'; cell.style.height = Math.floor(MATRIXSOLFEHEIGHT * this._cellScale) + 'px'; cell.className = 'headcol'; if (_THIS_IS_MUSIC_BLOCKS_) { // + 1 is for the note column for (var i = 0; i < this._logo.statusFields.length + 1; i++) { var cell = row.insertCell(); cell.style.backgroundColor = MATRIXRHYTHMCELLCOLOR; cell.style.fontSize = Math.floor(this._cellScale * 100) + '%'; cell.innerHTML = ''; cell.style.height = Math.floor(MATRIXSOLFEHEIGHT * this._cellScale) + 'px'; } } else { for (var i = 0; i < this._logo.statusFields.length; i++) { var cell = row.insertCell(); cell.style.backgroundColor = MATRIXRHYTHMCELLCOLOR; cell.style.fontSize = Math.floor(this._cellScale * 100) + '%'; cell.innerHTML = ''; cell.style.height = Math.floor(MATRIXSOLFEHEIGHT * this._cellScale) + 'px'; } } activeTurtles += 1; } }; this.updateAll = function() { // Update status of all of the voices in the matrix. var table = docById('statusTable'); this._logo.updatingStatusMatrix = true; var activeTurtles = 0; for (var turtle = 0; turtle < this._logo.turtles.turtleList.length; turtle++) { if (this._logo.turtles.turtleList[turtle].trash) { continue; } for (var i = 0; i < this._logo.statusFields.length; i++) { var saveStatus = this._logo.inStatusMatrix; this._logo.inStatusMatrix = false; this._logo.parseArg(this._logo, turtle, this._logo.statusFields[i][0]); switch (this._logo.blocks.blockList[this._logo.statusFields[i][0]].name) { case 'x': case 'y': case 'heading': var value = this._logo.blocks.blockList[this._logo.statusFields[i][0]].value.toFixed(2); break; case 'elapsednotes': var value = mixedNumber(this._logo.blocks.blockList[this._logo.statusFields[i][0]].value); break; case 'namedbox': var name = this._logo.blocks.blockList[this._logo.statusFields[i][0]].privateData; if (name in this._logo.boxes) { var value = this._logo.boxes[name]; } else { var value = ''; } break; default: var value = this._logo.blocks.blockList[this._logo.statusFields[i][0]].value; break; } var innerHTML = value; this._logo.inStatusMatrix = saveStatus; var cell = table.rows[activeTurtles + 1].cells[i + 1]; if (cell != null) { cell.innerHTML = innerHTML; } } if (_THIS_IS_MUSIC_BLOCKS_) { var note = ''; var value = ''; if (this._logo.noteStatus[turtle] != null) { var notes = this._logo.noteStatus[turtle][0]; for (var j = 0; j < notes.length; j++) { note += notes[j]; if (typeof(notes[j]) === 'number') { note += 'Hz '; } else { note += ' '; } } var value = this._logo.noteStatus[turtle][1]; var obj = rationalToFraction(value); note += obj[1] + '/' + obj[0]; } var cell = table.rows[activeTurtles + 1].cells[i + 1]; if (cell != null) { cell.innerHTML = note.replace(/#/g, '♯').replace(/b/, '♭'); } } activeTurtles += 1; } this._logo.updatingStatusMatrix = false; }; this._addButton = function(row, icon, iconSize, label) { var cell = row.insertCell(-1); cell.innerHTML = '  ' + label + '  '; cell.style.width = BUTTONSIZE + 'px'; cell.style.minWidth = cell.style.width; cell.style.maxWidth = cell.style.width; cell.style.height = cell.style.width; cell.style.minHeight = cell.style.height; cell.style.maxHeight = cell.style.height; cell.style.backgroundColor = MATRIXBUTTONCOLOR; cell.onmouseover=function() { this.style.backgroundColor = MATRIXBUTTONCOLORHOVER; } cell.onmouseout=function() { this.style.backgroundColor = MATRIXBUTTONCOLOR; } return cell; }; };