From bb879167632e51e6d3b30a13959190564f9eafca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Thu, 18 Jan 2018 09:56:22 +0100 Subject: [PATCH 01/11] Add drag&replace for new blocks and existing blocks --- app/scripts/graphics/joint.command.js | 32 ++++++------- app/scripts/graphics/joint.selection.js | 24 ++++++---- app/scripts/services/graph.js | 61 +++++++++++++++++++++---- 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/app/scripts/graphics/joint.command.js b/app/scripts/graphics/joint.command.js index 55da16a69..24c10cfe8 100755 --- a/app/scripts/graphics/joint.command.js +++ b/app/scripts/graphics/joint.command.js @@ -178,7 +178,7 @@ joint.dia.CommandManager = Backbone.Model.extend({ initBatchCommand: function() { - //console.log('initBatchCommand', this.batchCommand); + // console.log('initBatchCommand', this.batchCommand); if (!this.batchCommand) { @@ -198,30 +198,30 @@ joint.dia.CommandManager = Backbone.Model.extend({ storeBatchCommand: function() { - //console.log('storeBatchCommand', this.batchCommand, this.batchLevel); + // console.log('storeBatchCommand', this.batchCommand, this.batchLevel); // In order to store batch command it is necesary to run storeBatchCommand as many times as // initBatchCommand was executed if (this.batchCommand && this.batchLevel <= 0) { - // checking if there is any valid command in batch - // for example: calling `initBatchCommand` immediately followed by `storeBatchCommand` - if (this.lastCmdIndex >= 0) { + // checking if there is any valid command in batch + // for example: calling `initBatchCommand` immediately followed by `storeBatchCommand` + if (this.lastCmdIndex >= 0) { - this.redoStack = []; + this.redoStack = []; - this.undoStack.push(this.batchCommand); - if (this.batchCommand && this.batchCommand[0] && this.batchCommand[0].action !== 'lang') { - // Do not store lang in changesStack - this.changesStack.push(this.batchCommand); - this.triggerChange(); + this.undoStack.push(this.batchCommand); + if (this.batchCommand && this.batchCommand[0] && this.batchCommand[0].action !== 'lang') { + // Do not store lang in changesStack + this.changesStack.push(this.batchCommand); + this.triggerChange(); + } + this.trigger('add', this.batchCommand); } - this.trigger('add', this.batchCommand); - } - delete this.batchCommand; - delete this.lastCmdIndex; - delete this.batchLevel; + delete this.batchCommand; + delete this.lastCmdIndex; + delete this.batchLevel; } else if (this.batchCommand && this.batchLevel > 0) { diff --git a/app/scripts/graphics/joint.selection.js b/app/scripts/graphics/joint.selection.js index a33cb2c49..b48bb40b8 100644 --- a/app/scripts/graphics/joint.selection.js +++ b/app/scripts/graphics/joint.selection.js @@ -103,18 +103,16 @@ joint.ui.SelectionView = Backbone.View.extend({ } }, - startTranslatingSelection: function(evt, noBatch) { + startTranslatingSelection: function(evt) { - if (evt.which === 1 || noBatch) { + if (this._action !== 'adding' && evt.which === 1) { // Mouse left button if (!evt.shiftKey) { this._action = 'translating'; - if (!noBatch) { - this.options.graph.trigger('batch:stop'); - this.options.graph.trigger('batch:start'); - } + this.options.graph.trigger('batch:stop'); + this.options.graph.trigger('batch:start'); var snappedClientCoords = this.options.paper.snapToGrid(g.point(evt.clientX, evt.clientY)); this._snappedClientX = snappedClientCoords.x; @@ -125,9 +123,15 @@ joint.ui.SelectionView = Backbone.View.extend({ } }, - isTranslating: function() { + startAddingSelection: function(evt) { + + this._action = 'adding'; - return this._action === 'translating'; + var snappedClientCoords = this.options.paper.snapToGrid(g.point(evt.clientX, evt.clientY)); + this._snappedClientX = snappedClientCoords.x; + this._snappedClientY = snappedClientCoords.y; + + this.trigger('selection-box:pointerdown', evt); }, startSelecting: function(evt/*, x, y*/) { @@ -179,6 +183,7 @@ joint.ui.SelectionView = Backbone.View.extend({ }); break; + case 'adding': case 'translating': var snappedClientCoords = this.options.paper.snapToGrid(g.point(evt.clientX, evt.clientY)); @@ -281,6 +286,9 @@ joint.ui.SelectionView = Backbone.View.extend({ // Everything else is done during the translation. break; + case 'adding': + break; + case 'cherry-picking': // noop; All is done in the `createSelectionBox()` function. // This is here to avoid removing selection boxes as a reaction on mouseup event and diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index 759e48513..059770e6b 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -320,8 +320,10 @@ angular.module('icestudio') if (self.addingDraggableBlock) { // Set new block position self.addingDraggableBlock = false; + processReplaceBlock(selection.at(0)); disableSelected(); updateWiresOnObstacles(); + graph.trigger('batch:stop'); } else { // Toggle selected cell @@ -454,6 +456,55 @@ angular.module('icestudio') } }); + paper.on('cell:pointerup', function(cellView/*, evt*/) { + graph.trigger('batch:start'); + processReplaceBlock(cellView.model); + graph.trigger('batch:stop'); + }); + + function processReplaceBlock(upperBlock) { + console.log(upperBlock); + if (upperBlock.get('type') !== 'ice.Generic' && + upperBlock.get('type') !== 'ice.Code') { + return; + } + var blocks = graph.findModelsUnderElement(upperBlock); + if (blocks.length > 0) { + // There is at least one model ice.Generic under the upperModel + // Get the first model found + var lowerBlock = blocks[0]; + if (lowerBlock.get('type') !== 'ice.Generic' && + lowerBlock.get('type') !== 'ice.Code') { + return; + } + // Check ports interface + // Reconnect the wires from the lowerModel to the upperModel + var wires = graph.getConnectedLinks(lowerBlock); + console.log(wires); + + _.each(wires, function(wire) { + // 1. + wire.set('source', { id: upperBlock.get('id'), port: upperBlock.get('rightPorts')[0].id }); + }); + // Move the upperModel to the lowerModel's position + upperBlock.set('position', lowerBlock.get('position')); + // Remove the lowerModel + lowerBlock.remove(); + } + } + + paper.on('cell:mousemove', function(/*cellView, evt*/) { + // console.log('MOVE'); + // console.log(cellView, evt); + }); + + graph.on('change:position', function(/*cell*/) { + // if (!hasSelection()) { + // console.log('CHANGE POSITION'); + // console.log(cell); + // } + }); + /*paper.on('cell:mouseout', function(cellView, evt) { if (!utils.hasButtonPressed(evt)) { if (!cellView.model.isLink()) { @@ -472,10 +523,6 @@ angular.module('icestudio') }); graph.on('change:position', function(cell) { - if (!selectionView.isTranslating()) { - // Update wires on obstacles motion - updateWiresOnObstacles(); - } });*/ graph.on('add change:source change:target', function(cell) { @@ -629,10 +676,9 @@ angular.module('icestudio') addCell(cell); disableSelected(); var opt = { transparent: true, initooltip: false }; - var noBatch = true; selection.add(cell); selectionView.createSelectionBox(cell, opt); - selectionView.startTranslatingSelection({ clientX: mousePosition.x, clientY: mousePosition.y }, noBatch); + selectionView.startAddingSelection({ clientX: mousePosition.x, clientY: mousePosition.y }); }; this.addDraggableCells = function(cells) { @@ -655,12 +701,11 @@ angular.module('icestudio') addCells(cells); disableSelected(); var opt = { transparent: true }; - var noBatch = true; _.each(cells, function(cell) { selection.add(cell); selectionView.createSelectionBox(cell, opt); }); - selectionView.startTranslatingSelection({ clientX: mousePosition.x, clientY: mousePosition.y }, noBatch); + selectionView.startAddingSelection({ clientX: mousePosition.x, clientY: mousePosition.y }); } }; From 21ee1866050588a1adfd26faa2588c880835eda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Fri, 19 Jan 2018 17:36:56 +0100 Subject: [PATCH 02/11] Detect "replace block" situation --- app/scripts/graphics/joint.command.js | 4 +- app/scripts/graphics/joint.selection.js | 2 + app/scripts/services/graph.js | 183 +++++++++--------------- 3 files changed, 69 insertions(+), 120 deletions(-) diff --git a/app/scripts/graphics/joint.command.js b/app/scripts/graphics/joint.command.js index 24c10cfe8..077130cdc 100755 --- a/app/scripts/graphics/joint.command.js +++ b/app/scripts/graphics/joint.command.js @@ -249,7 +249,9 @@ joint.dia.CommandManager = Backbone.Model.extend({ switch (cmd.action) { case 'add': - cell.remove(); + if (cell) { + cell.remove(); + } break; case 'remove': diff --git a/app/scripts/graphics/joint.selection.js b/app/scripts/graphics/joint.selection.js index b48bb40b8..61d0086d0 100644 --- a/app/scripts/graphics/joint.selection.js +++ b/app/scripts/graphics/joint.selection.js @@ -238,6 +238,8 @@ joint.ui.SelectionView = Backbone.View.extend({ this._snappedClientY = snappedClientY; } + this.trigger('selection-box:pointermove', evt); + break; } }, diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index 059770e6b..120f6bd52 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -31,6 +31,9 @@ angular.module('icestudio') this.breadcrumbs = [{ name: '', type: '' }]; this.addingDraggableBlock = false; + this.upperBlock = null; + this.lowerBlock = null; + // Functions this.getState = function() { @@ -304,7 +307,7 @@ angular.module('icestudio') selectionView.on('selection-box:pointerdown', function(/*evt*/) { // Move selection to top view - if (selection) { + if (hasSelection()) { selection.each(function(cell) { var cellView = paper.findViewByModel(cell); if (!cellView.model.isLink()) { @@ -318,9 +321,10 @@ angular.module('icestudio') selectionView.on('selection-box:pointerclick', function(evt) { if (self.addingDraggableBlock) { - // Set new block position + // Set new block's position self.addingDraggableBlock = false; - processReplaceBlock(selection.at(0)); + processReplaceBlock(); + resetReplaceBlock(); disableSelected(); updateWiresOnObstacles(); graph.trigger('batch:stop'); @@ -332,7 +336,7 @@ angular.module('icestudio') selection.reset(selection.without(cell)); selectionView.destroySelectionBox(cell); } - } + } }); var pointerDown = false; @@ -367,7 +371,6 @@ angular.module('icestudio') // Add cell to selection selection.add(cellView.model); selectionView.createSelectionBox(cellView.model); - //unhighlight(cellView); } } } @@ -448,7 +451,6 @@ angular.module('icestudio') // Move selection to top view if !mousedown if (!utils.hasButtonPressed(evt)) { if (!cellView.model.isLink()) { - //highlight(cellView); if (cellView.$box.css('z-index') < z.index) { cellView.$box.css('z-index', ++z.index); } @@ -456,74 +458,85 @@ angular.module('icestudio') } }); - paper.on('cell:pointerup', function(cellView/*, evt*/) { + paper.on('cell:pointerup', function(/*cellView, evt*/) { graph.trigger('batch:start'); - processReplaceBlock(cellView.model); + processReplaceBlock(); + resetReplaceBlock(); graph.trigger('batch:stop'); }); - function processReplaceBlock(upperBlock) { - console.log(upperBlock); - if (upperBlock.get('type') !== 'ice.Generic' && - upperBlock.get('type') !== 'ice.Code') { - return; + var highlightReplaceBlock = _.debounce(function(newUpperBlock) { + var newLowerBlock = findLowerBlock(newUpperBlock); + + if (self.lowerBlock) { + // Unhighlight previous lower block + self.lowerBlock.set('size', {width: 96, height: 64}); } - var blocks = graph.findModelsUnderElement(upperBlock); - if (blocks.length > 0) { - // There is at least one model ice.Generic under the upperModel - // Get the first model found - var lowerBlock = blocks[0]; - if (lowerBlock.get('type') !== 'ice.Generic' && - lowerBlock.get('type') !== 'ice.Code') { - return; - } + if (newLowerBlock) { + // Highlight new lower block + newLowerBlock.set('size', {width: 116, height: 64}); + } + self.upperBlock = newUpperBlock; + self.lowerBlock = newLowerBlock; + + }, 100); + + function resetReplaceBlock() { + highlightReplaceBlock.cancel(); + self.upperBlock = null; + self.lowerBlock = null; + } + + function processReplaceBlock() { + if (self.upperBlock && self.lowerBlock) { + console.log(self.upperBlock, self.lowerBlock); // Check ports interface // Reconnect the wires from the lowerModel to the upperModel - var wires = graph.getConnectedLinks(lowerBlock); + var wires = graph.getConnectedLinks(self.lowerBlock); console.log(wires); _.each(wires, function(wire) { // 1. - wire.set('source', { id: upperBlock.get('id'), port: upperBlock.get('rightPorts')[0].id }); + wire.set('source', { id: self.upperBlock.get('id'), port: self.upperBlock.get('rightPorts')[0].id }); }); // Move the upperModel to the lowerModel's position - upperBlock.set('position', lowerBlock.get('position')); + self.upperBlock.set('position', self.lowerBlock.get('position')); // Remove the lowerModel - lowerBlock.remove(); + self.lowerBlock.remove(); } } - paper.on('cell:mousemove', function(/*cellView, evt*/) { - // console.log('MOVE'); - // console.log(cellView, evt); + paper.on('cell:pointermove', function(cellView/*, evt*/) { + highlightReplaceBlock(cellView.model); }); - graph.on('change:position', function(/*cell*/) { - // if (!hasSelection()) { - // console.log('CHANGE POSITION'); - // console.log(cell); - // } + selectionView.on('selection-box:pointermove', function(/*evt*/) { + if (self.addingDraggableBlock && hasSelection()) { + highlightReplaceBlock(selection.at(0)); + } }); - /*paper.on('cell:mouseout', function(cellView, evt) { - if (!utils.hasButtonPressed(evt)) { - if (!cellView.model.isLink()) { - unhighlight(cellView); - } + function findLowerBlock(newUpperBlock) { + if (newUpperBlock.get('type') !== 'ice.Generic' && + newUpperBlock.get('type') !== 'ice.Code' && + newUpperBlock.get('type') !== 'ice.Input') { + return; } - });*/ - - /*paper.on('cell:pointerdown', function(cellView) { - if (paper.options.enabled) { - if (cellView.model.isLink()) { - // Unhighlight source block of the wire - unhighlight(paper.findViewByModel(cellView.model.get('source').id)); - } + var blocks = graph.findModelsUnderElement(newUpperBlock); + // There is at least one model ice.Generic under the upperModel + if (blocks.length <= 0) { + return; } - }); - - graph.on('change:position', function(cell) { - });*/ + // Get the first model found + var newLowerBlock = null; + newLowerBlock = blocks[0]; + if (newLowerBlock.get('type') !== 'ice.Generic' && + newLowerBlock.get('type') !== 'ice.Code' && + newLowerBlock.get('type') !== 'ice.Input') { + return; + } + return newLowerBlock; + } graph.on('add change:source change:target', function(cell) { if (cell.isLink() && cell.get('source').id) { @@ -866,77 +879,10 @@ angular.module('icestudio') if (!cell.isLink()) { selection.add(cell); selectionView.createSelectionBox(cell); - //unhighlight(cellView); } }); }; - /*function highlight(cellView) { - if (cellView) { - switch(cellView.model.get('type')) { - case 'ice.Input': - case 'ice.Output': - if (cellView.model.get('data').virtual) { - cellView.$box.addClass('highlight-green'); - } - else { - cellView.$box.addClass('highlight-yellow'); - } - break; - case 'ice.Constant': - cellView.$box.addClass('highlight-orange'); - break; - case 'ice.Code': - cellView.$box.addClass('highlight-blue'); - break; - case 'ice.Generic': - if (cellView.model.get('config')) { - cellView.$box.addClass('highlight-yellow'); - } - else { - cellView.$box.addClass('highlight-blue'); - } - break; - case 'ice.Info': - cellView.$box.addClass('highlight-gray'); - break; - } - } - } - - function unhighlight(cellView) { - if (cellView) { - switch(cellView.model.get('type')) { - case 'ice.Input': - case 'ice.Output': - if (cellView.model.get('data').virtual) { - cellView.$box.removeClass('highlight-green'); - } - else { - cellView.$box.removeClass('highlight-yellow'); - } - break; - case 'ice.Constant': - cellView.$box.removeClass('highlight-orange'); - break; - case 'ice.Code': - cellView.$box.removeClass('highlight-blue'); - break; - case 'ice.Generic': - if (cellView.model.get('config')) { - cellView.$box.removeClass('highlight-yellow'); - } - else { - cellView.$box.removeClass('highlight-blue'); - } - break; - case 'ice.Info': - cellView.$box.removeClass('highlight-gray'); - break; - } - } - }*/ - function hasSelection() { return selection && selection.length > 0; } @@ -1198,7 +1144,6 @@ angular.module('icestudio') } selection.add(cell); selectionView.createSelectionBox(cell); - //unhighlight(cellView); } }); } From 009b0a4e55eb23ed9c1b9b9803c748e2a6b529c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sat, 20 Jan 2018 11:44:43 +0100 Subject: [PATCH 03/11] Disable replaced block --- app/package.json | 3 +- app/scripts/factories/node.js | 3 + app/scripts/services/graph.js | 108 +++++++++++++++++----------------- app/styles/design.css | 10 ++++ 4 files changed, 70 insertions(+), 54 deletions(-) diff --git a/app/package.json b/app/package.json index 0aa8ccd64..8068580c9 100644 --- a/app/package.json +++ b/app/package.json @@ -12,7 +12,7 @@ "height": 620, "min_width": 800, "min_height": 200, - "toolbar": false, + "toolbar": true, "resizable": true, "position": "center", "icon": "resources/images/icestudio-logo.png" @@ -41,6 +41,7 @@ "glob": "^7.1.2", "is-online": "^5.2.0", "jquery": "^3.2.1", + "lodash.debounce": "^4.0.8", "marked": "^0.3.12", "node-emoji": "^1.8.1", "node-lang-info": "^0.2.1", diff --git a/app/scripts/factories/node.js b/app/scripts/factories/node.js index f8aa6e1b7..b18a78a05 100644 --- a/app/scripts/factories/node.js +++ b/app/scripts/factories/node.js @@ -58,6 +58,9 @@ angular.module('icestudio') .factory('nodeTemp', function() { return require('temporary'); }) + .factory('nodeDebounce', function() { + return require('lodash.debounce'); + }) .factory('SVGO', function() { var config = { full: true, diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index 120f6bd52..98c388fd1 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -9,6 +9,7 @@ angular.module('icestudio') utils, common, gettextCatalog, + nodeDebounce, window) { // Variables @@ -31,9 +32,6 @@ angular.module('icestudio') this.breadcrumbs = [{ name: '', type: '' }]; this.addingDraggableBlock = false; - this.upperBlock = null; - this.lowerBlock = null; - // Functions this.getState = function() { @@ -323,8 +321,7 @@ angular.module('icestudio') if (self.addingDraggableBlock) { // Set new block's position self.addingDraggableBlock = false; - processReplaceBlock(); - resetReplaceBlock(); + processReplaceBlock(selection.at(0)); disableSelected(); updateWiresOnObstacles(); graph.trigger('batch:stop'); @@ -458,86 +455,91 @@ angular.module('icestudio') } }); - paper.on('cell:pointerup', function(/*cellView, evt*/) { + paper.on('cell:pointerup', function(cellView/*, evt*/) { graph.trigger('batch:start'); - processReplaceBlock(); - resetReplaceBlock(); + processReplaceBlock(cellView.model); graph.trigger('batch:stop'); }); - var highlightReplaceBlock = _.debounce(function(newUpperBlock) { - var newLowerBlock = findLowerBlock(newUpperBlock); + paper.on('cell:pointermove', function(cellView/*, evt*/) { + debounceDisableReplacedBlock(cellView.model); + }); - if (self.lowerBlock) { - // Unhighlight previous lower block - self.lowerBlock.set('size', {width: 96, height: 64}); - } - if (newLowerBlock) { - // Highlight new lower block - newLowerBlock.set('size', {width: 116, height: 64}); + selectionView.on('selection-box:pointermove', function(/*evt*/) { + if (self.addingDraggableBlock && hasSelection()) { + debounceDisableReplacedBlock(selection.at(0)); } - self.upperBlock = newUpperBlock; - self.lowerBlock = newLowerBlock; - - }, 100); + }); - function resetReplaceBlock() { - highlightReplaceBlock.cancel(); - self.upperBlock = null; - self.lowerBlock = null; + function processReplaceBlock(upperBlock) { + debounceDisableReplacedBlock.flush(); + var lowerBlock = findLowerBlock(upperBlock); + replaceBlock(upperBlock, lowerBlock); } - function processReplaceBlock() { - if (self.upperBlock && self.lowerBlock) { - console.log(self.upperBlock, self.lowerBlock); + var prevLowerBlock = null; + + function replaceBlock(upperBlock, lowerBlock) { + if (lowerBlock) { + console.log(upperBlock, lowerBlock); + // Check ports interface // Reconnect the wires from the lowerModel to the upperModel - var wires = graph.getConnectedLinks(self.lowerBlock); + var wires = graph.getConnectedLinks(lowerBlock); console.log(wires); _.each(wires, function(wire) { // 1. - wire.set('source', { id: self.upperBlock.get('id'), port: self.upperBlock.get('rightPorts')[0].id }); + wire.set('source', { id: upperBlock.get('id'), port: upperBlock.get('rightPorts')[0].id }); }); // Move the upperModel to the lowerModel's position - self.upperBlock.set('position', self.lowerBlock.get('position')); + upperBlock.set('position', lowerBlock.get('position')); // Remove the lowerModel - self.lowerBlock.remove(); + lowerBlock.remove(); + prevLowerBlock = null; } } - paper.on('cell:pointermove', function(cellView/*, evt*/) { - highlightReplaceBlock(cellView.model); - }); - - selectionView.on('selection-box:pointermove', function(/*evt*/) { - if (self.addingDraggableBlock && hasSelection()) { - highlightReplaceBlock(selection.at(0)); - } - }); - - function findLowerBlock(newUpperBlock) { - if (newUpperBlock.get('type') !== 'ice.Generic' && - newUpperBlock.get('type') !== 'ice.Code' && - newUpperBlock.get('type') !== 'ice.Input') { + function findLowerBlock(upperBlock) { + if (upperBlock.get('type') !== 'ice.Generic' && + upperBlock.get('type') !== 'ice.Code' && + upperBlock.get('type') !== 'ice.Input') { return; } - var blocks = graph.findModelsUnderElement(newUpperBlock); + var blocks = graph.findModelsUnderElement(upperBlock); // There is at least one model ice.Generic under the upperModel if (blocks.length <= 0) { return; } // Get the first model found - var newLowerBlock = null; - newLowerBlock = blocks[0]; - if (newLowerBlock.get('type') !== 'ice.Generic' && - newLowerBlock.get('type') !== 'ice.Code' && - newLowerBlock.get('type') !== 'ice.Input') { + var lowerBlock = null; + lowerBlock = blocks[0]; + if (lowerBlock.get('type') !== 'ice.Generic' && + lowerBlock.get('type') !== 'ice.Code' && + lowerBlock.get('type') !== 'ice.Input') { return; } - return newLowerBlock; + return lowerBlock; + } + + function disableReplacedBlock(lowerBlock) { + if (prevLowerBlock) { + // Unhighlight previous lower block + paper.findViewByModel(prevLowerBlock).$box.removeClass('block-disabled'); + } + if (lowerBlock) { + // Highlight new lower block + paper.findViewByModel(lowerBlock).$box.addClass('block-disabled'); + } + prevLowerBlock = lowerBlock; } + // Debounce `pointermove` handler to improve the performance + var debounceDisableReplacedBlock = nodeDebounce(function (upperBlock) { + var lowerBlock = findLowerBlock(upperBlock); + disableReplacedBlock(lowerBlock); + }, 100); + graph.on('add change:source change:target', function(cell) { if (cell.isLink() && cell.get('source').id) { // Link connected diff --git a/app/styles/design.css b/app/styles/design.css index d02ed0b2f..79634d7aa 100644 --- a/app/styles/design.css +++ b/app/styles/design.css @@ -104,6 +104,10 @@ pointer-events: none; } +.block-disabled { + opacity: 0.4; +} + .generic-block { position: absolute; background: #C0DFEB; @@ -113,6 +117,7 @@ -webkit-user-select: none; user-select: none; z-index: 0; + transition: opacity 0.2s; } .generic-block .clock { @@ -194,6 +199,7 @@ -webkit-user-select: none; user-select: none; z-index: 0; + transition: opacity 0.2s; } .virtual-port p { @@ -225,6 +231,7 @@ -webkit-user-select: none; user-select: none; z-index: 0; + transition: opacity 0.2s; } .fpga-port p { @@ -304,6 +311,7 @@ -webkit-user-select: none; user-select: none; z-index: 0; + transition: opacity 0.2s; } .constant-block p { @@ -353,6 +361,7 @@ -webkit-user-select: none; user-select: none; z-index: 0; + transition: opacity 0.2s; } .code-block .code-editor { @@ -376,6 +385,7 @@ -webkit-user-select: none; user-select: none; z-index: 0; + transition: opacity 0.2s; } .info-block-readonly { From 139a481a5f2b3677a9d64ea55f73f93d49ce62ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sat, 20 Jan 2018 12:00:46 +0100 Subject: [PATCH 04/11] Improve "disable replace" effect in svg layer --- app/scripts/graphics/joint.shapes.js | 8 ++++---- app/scripts/services/graph.js | 8 ++++++-- app/styles/design.css | 4 ++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/scripts/graphics/joint.shapes.js b/app/scripts/graphics/joint.shapes.js index 7455e4e18..9fe3531b0 100644 --- a/app/scripts/graphics/joint.shapes.js +++ b/app/scripts/graphics/joint.shapes.js @@ -190,7 +190,7 @@ joint.shapes.ice.Model = joint.shapes.basic.Generic.extend({ attrs[portLabelSelector]['y'] = -5-offset; attrs[portLabelSelector]['text-anchor'] = 'end'; attrs[portWireSelector]['y'] = position; - attrs[portWireSelector]['d'] = 'M 0 0 L 16 0'; + attrs[portWireSelector]['d'] = 'M 0 0 L 8 0'; break; case 'right': attrs[portSelector]['ref-dx'] = 8; @@ -199,7 +199,7 @@ joint.shapes.ice.Model = joint.shapes.basic.Generic.extend({ attrs[portLabelSelector]['y'] = -5-offset; attrs[portLabelSelector]['text-anchor'] = 'start'; attrs[portWireSelector]['y'] = position; - attrs[portWireSelector]['d'] = 'M 0 0 L -16 0'; + attrs[portWireSelector]['d'] = 'M 0 0 L -8 0'; break; case 'top': attrs[portSelector]['ref-y'] = -8; @@ -208,7 +208,7 @@ joint.shapes.ice.Model = joint.shapes.basic.Generic.extend({ attrs[portLabelSelector]['y'] = 2; attrs[portLabelSelector]['text-anchor'] = 'start'; attrs[portWireSelector]['x'] = position; - attrs[portWireSelector]['d'] = 'M 0 0 L 0 16'; + attrs[portWireSelector]['d'] = 'M 0 0 L 0 8'; break; case 'bottom': attrs[portSelector]['ref-dy'] = 8; @@ -217,7 +217,7 @@ joint.shapes.ice.Model = joint.shapes.basic.Generic.extend({ attrs[portLabelSelector]['y'] = -2; attrs[portLabelSelector]['text-anchor'] = 'start'; attrs[portWireSelector]['x'] = position; - attrs[portWireSelector]['d'] = 'M 0 0 L 0 -16'; + attrs[portWireSelector]['d'] = 'M 0 0 L 0 -8'; break; } diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index 98c388fd1..4217a8770 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -525,11 +525,15 @@ angular.module('icestudio') function disableReplacedBlock(lowerBlock) { if (prevLowerBlock) { // Unhighlight previous lower block - paper.findViewByModel(prevLowerBlock).$box.removeClass('block-disabled'); + var prevLowerBlockView = paper.findViewByModel(prevLowerBlock); + prevLowerBlockView.$box.removeClass('block-disabled'); + prevLowerBlockView.$el.removeClass('block-disabled'); } if (lowerBlock) { // Highlight new lower block - paper.findViewByModel(lowerBlock).$box.addClass('block-disabled'); + var lowerBlockView = paper.findViewByModel(lowerBlock); + lowerBlockView.$box.addClass('block-disabled'); + lowerBlockView.$el.addClass('block-disabled'); } prevLowerBlock = lowerBlock; } diff --git a/app/styles/design.css b/app/styles/design.css index 79634d7aa..3cad275d8 100644 --- a/app/styles/design.css +++ b/app/styles/design.css @@ -108,6 +108,10 @@ opacity: 0.4; } +g { + transition: opacity 0.2s; +} + .generic-block { position: absolute; background: #C0DFEB; From 39ff5932b2863c1ad840701b26c9ca9eba39d432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sat, 20 Jan 2018 16:38:04 +0100 Subject: [PATCH 05/11] Add wires replacement in block replacement --- app/package.json | 2 +- app/scripts/services/graph.js | 60 ++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/app/package.json b/app/package.json index 8068580c9..20a323d50 100644 --- a/app/package.json +++ b/app/package.json @@ -12,7 +12,7 @@ "height": 620, "min_width": 800, "min_height": 200, - "toolbar": true, + "toolbar": false, "resizable": true, "position": "center", "icon": "resources/images/icestudio-logo.png" diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index 4217a8770..79c618798 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -481,23 +481,60 @@ angular.module('icestudio') function replaceBlock(upperBlock, lowerBlock) { if (lowerBlock) { - console.log(upperBlock, lowerBlock); - - // Check ports interface - // Reconnect the wires from the lowerModel to the upperModel + // 1. Compute portsMap between the upperBlock and the lowerBlock + var portsMap = computePortsMap(upperBlock, lowerBlock); + // 2. Reconnect the wires from the lowerBlock to the upperBlock var wires = graph.getConnectedLinks(lowerBlock); - console.log(wires); - _.each(wires, function(wire) { - // 1. - wire.set('source', { id: upperBlock.get('id'), port: upperBlock.get('rightPorts')[0].id }); + // Replace wire's source + replaceWireConnection(wire, 'source'); + // Replace wire's target + replaceWireConnection(wire, 'target'); }); - // Move the upperModel to the lowerModel's position + // 3. Move the upperModel to the lowerModel's position upperBlock.set('position', lowerBlock.get('position')); - // Remove the lowerModel + // 4. Remove the lowerModel lowerBlock.remove(); prevLowerBlock = null; } + + function replaceWireConnection(wire, connectorType) { + var connector = wire.get(connectorType); + if (connector.id === lowerBlock.get('id') && portsMap[connector.port]) { + wire.set(connectorType, { + id: upperBlock.get('id'), + port: portsMap[connector.port] + }); + } + } + } + + function computePortsMap(upperBlock, lowerBlock) { + var portsMap = {}; + var upperPorts = []; + var lowerPorts = []; + + upperPorts = upperPorts.concat(upperBlock.get('leftPorts')); + upperPorts = upperPorts.concat(upperBlock.get('rightPorts')); + upperPorts = upperPorts.concat(upperBlock.get('topPorts')); + + lowerPorts = lowerPorts.concat(lowerBlock.get('leftPorts')); + lowerPorts = lowerPorts.concat(lowerBlock.get('rightPorts')); + lowerPorts = lowerPorts.concat(lowerBlock.get('topPorts')); + + _.each(lowerPorts, function(lowerPort) { + if (!_.has(portsMap, lowerPort.id)) { + var matchedPorts = _.filter(upperPorts, function(upperPort) { + return lowerPort.name === upperPort.name && + lowerPort.size === upperPort.size; + }); + if (matchedPorts && matchedPorts.length > 0) { + portsMap[lowerPort.id] = matchedPorts[0].id; + } + } + }); + + return portsMap; } function findLowerBlock(upperBlock) { @@ -512,8 +549,7 @@ angular.module('icestudio') return; } // Get the first model found - var lowerBlock = null; - lowerBlock = blocks[0]; + var lowerBlock = blocks[0]; if (lowerBlock.get('type') !== 'ice.Generic' && lowerBlock.get('type') !== 'ice.Code' && lowerBlock.get('type') !== 'ice.Input') { From a06aba59e63a8ee92c153f0076d5e69ecfa0aa7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sat, 20 Jan 2018 21:29:10 +0100 Subject: [PATCH 06/11] Replace wires by position if there are ports with the same name --- app/scripts/services/graph.js | 93 ++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index 79c618798..c13c3e8ee 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -477,12 +477,32 @@ angular.module('icestudio') replaceBlock(upperBlock, lowerBlock); } - var prevLowerBlock = null; + function findLowerBlock(upperBlock) { + if (upperBlock.get('type') !== 'ice.Generic' && + upperBlock.get('type') !== 'ice.Code' && + upperBlock.get('type') !== 'ice.Input') { + return; + } + var blocks = graph.findModelsUnderElement(upperBlock); + // There is at least one model ice.Generic under the upperModel + if (blocks.length <= 0) { + return; + } + // Get the first model found + var lowerBlock = blocks[0]; + if (lowerBlock.get('type') !== 'ice.Generic' && + lowerBlock.get('type') !== 'ice.Code' && + lowerBlock.get('type') !== 'ice.Input') { + return; + } + return lowerBlock; + } function replaceBlock(upperBlock, lowerBlock) { if (lowerBlock) { // 1. Compute portsMap between the upperBlock and the lowerBlock - var portsMap = computePortsMap(upperBlock, lowerBlock); + var portsMap = computeAllPortsMap(upperBlock, lowerBlock); + console.log(portsMap); // 2. Reconnect the wires from the lowerBlock to the upperBlock var wires = graph.getConnectedLinks(lowerBlock); _.each(wires, function(wire) { @@ -509,54 +529,49 @@ angular.module('icestudio') } } - function computePortsMap(upperBlock, lowerBlock) { + function computeAllPortsMap(upperBlock, lowerBlock) { var portsMap = {}; - var upperPorts = []; - var lowerPorts = []; - upperPorts = upperPorts.concat(upperBlock.get('leftPorts')); - upperPorts = upperPorts.concat(upperBlock.get('rightPorts')); - upperPorts = upperPorts.concat(upperBlock.get('topPorts')); + // Compute the ports for each side: left, right and top. + // If there are ports with the same name they are ordered + // by position, from 0 to n. + // + // Top ports 0 ·· n + // _____|__|__|_____ + // Left ports 0 --| |-- 0 Right ports + // · --| BLOCK |-- · + // · --| |-- · + // n |_________________| n + // + _.merge(portsMap, computePortsMap(upperBlock, lowerBlock, 'leftPorts')); + _.merge(portsMap, computePortsMap(upperBlock, lowerBlock, 'rightPorts')); + _.merge(portsMap, computePortsMap(upperBlock, lowerBlock, 'topPorts')); + + return portsMap; + } - lowerPorts = lowerPorts.concat(lowerBlock.get('leftPorts')); - lowerPorts = lowerPorts.concat(lowerBlock.get('rightPorts')); - lowerPorts = lowerPorts.concat(lowerBlock.get('topPorts')); + function computePortsMap(upperBlock, lowerBlock, portType) { + var portsMap = {}; + var usedUpperPorts = []; + var upperPorts = upperBlock.get(portType); + var lowerPorts = lowerBlock.get(portType); _.each(lowerPorts, function(lowerPort) { - if (!_.has(portsMap, lowerPort.id)) { - var matchedPorts = _.filter(upperPorts, function(upperPort) { - return lowerPort.name === upperPort.name && - lowerPort.size === upperPort.size; - }); - if (matchedPorts && matchedPorts.length > 0) { - portsMap[lowerPort.id] = matchedPorts[0].id; - } + var matchedPorts = _.filter(upperPorts, function(upperPort) { + return lowerPort.name === upperPort.name && + lowerPort.size === upperPort.size && + !_.includes(usedUpperPorts, upperPort); + }); + if (matchedPorts && matchedPorts.length > 0) { + portsMap[lowerPort.id] = matchedPorts[0].id; + usedUpperPorts = usedUpperPorts.concat(matchedPorts[0]); } }); return portsMap; } - function findLowerBlock(upperBlock) { - if (upperBlock.get('type') !== 'ice.Generic' && - upperBlock.get('type') !== 'ice.Code' && - upperBlock.get('type') !== 'ice.Input') { - return; - } - var blocks = graph.findModelsUnderElement(upperBlock); - // There is at least one model ice.Generic under the upperModel - if (blocks.length <= 0) { - return; - } - // Get the first model found - var lowerBlock = blocks[0]; - if (lowerBlock.get('type') !== 'ice.Generic' && - lowerBlock.get('type') !== 'ice.Code' && - lowerBlock.get('type') !== 'ice.Input') { - return; - } - return lowerBlock; - } + var prevLowerBlock = null; function disableReplacedBlock(lowerBlock) { if (prevLowerBlock) { From 0021681f62a24be776fed0e2e00cf164bd668572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sat, 20 Jan 2018 23:30:46 +0100 Subject: [PATCH 07/11] Add particular case for reconnecting wires: match length and size --- app/scripts/services/graph.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index c13c3e8ee..409184adb 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -568,6 +568,17 @@ angular.module('icestudio') } }); + if (_.isEmpty(portsMap) && upperPorts.length === lowerPorts.length) { + // If there is no match and the number of lowerPorts and + // upperPorts is the same, replace the connections if the + // size matches ignoring the ports's name. + for (var i = 0; i < lowerPorts.length; i++) { + if (lowerPorts[i].size === upperPorts[i].size) { + portsMap[lowerPorts[i].id] = upperPorts[i].id; + } + } + } + return portsMap; } From 36ac2988a46339ac63739f68a26bf2b79cf99927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sat, 20 Jan 2018 23:31:54 +0100 Subject: [PATCH 08/11] Update wires on obstacles after undo/redo --- app/scripts/services/graph.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index 409184adb..ec6196ce7 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -684,11 +684,13 @@ angular.module('icestudio') this.undo = function() { disableSelected(); commandManager.undo(); + updateWiresOnObstacles(); }; this.redo = function() { disableSelected(); commandManager.redo(); + updateWiresOnObstacles(); }; this.clearAll = function() { From cd3c397d7b0b88bf7c98263aa6a195324c81767b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sat, 20 Jan 2018 23:44:48 +0100 Subject: [PATCH 09/11] Update particular case: use min length --- app/scripts/services/graph.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index ec6196ce7..5c9068596 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -568,11 +568,11 @@ angular.module('icestudio') } }); - if (_.isEmpty(portsMap) && upperPorts.length === lowerPorts.length) { - // If there is no match and the number of lowerPorts and - // upperPorts is the same, replace the connections if the - // size matches ignoring the ports's name. - for (var i = 0; i < lowerPorts.length; i++) { + if (_.isEmpty(portsMap)) { + // If there is no match replace the connections if the + // port's size matches ignoring the port's name. + var n = Math.min(upperPorts.length, lowerPorts.length); + for (var i = 0; i < n; i++) { if (lowerPorts[i].size === upperPorts[i].size) { portsMap[lowerPorts[i].id] = upperPorts[i].id; } From 100603ee5735343dcdbc558983916fa98003275d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sat, 20 Jan 2018 23:45:11 +0100 Subject: [PATCH 10/11] Update min size for code blocks --- app/scripts/graphics/joint.shapes.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/scripts/graphics/joint.shapes.js b/app/scripts/graphics/joint.shapes.js index 9fe3531b0..d4cc87c98 100644 --- a/app/scripts/graphics/joint.shapes.js +++ b/app/scripts/graphics/joint.shapes.js @@ -296,10 +296,14 @@ joint.shapes.ice.ModelView = joint.dia.ElementView.extend({ return; } + var type = self.model.get('type'); var size = self.model.get('size'); var state = self.model.get('state'); var gridstep = 8 * 2; - var minSize = { width: 64, height: 32 }; + var minSize = { + width: type === 'ice.Code' ? 96 : 64, + height: type === 'ice.Code' ? 64 : 32 + }; var clientCoords = snapToGrid({ x: event.clientX, y: event.clientY }); var oldClientCoords = snapToGrid({ x: self._clientX, y: self._clientY }); From 00898a0a4fc81810a816492516a81df097c2e041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Arroyo=20Torrens?= Date: Sun, 21 Jan 2018 09:24:07 +0100 Subject: [PATCH 11/11] Remove console.log --- app/scripts/services/graph.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/services/graph.js b/app/scripts/services/graph.js index 5c9068596..6fa9064ad 100644 --- a/app/scripts/services/graph.js +++ b/app/scripts/services/graph.js @@ -502,7 +502,6 @@ angular.module('icestudio') if (lowerBlock) { // 1. Compute portsMap between the upperBlock and the lowerBlock var portsMap = computeAllPortsMap(upperBlock, lowerBlock); - console.log(portsMap); // 2. Reconnect the wires from the lowerBlock to the upperBlock var wires = graph.getConnectedLinks(lowerBlock); _.each(wires, function(wire) {