diff --git a/Gruntfile.js b/Gruntfile.js index 3738e60b4..6d53e4d29 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -144,6 +144,7 @@ module.exports = function(grunt) { "editor/js/ui/keyboard.js", "editor/js/ui/workspaces.js", "editor/js/ui/view.js", + "editor/js/ui/view-navigator.js", "editor/js/ui/sidebar.js", "editor/js/ui/palette.js", "editor/js/ui/tab-info.js", diff --git a/editor/js/ui/view-navigator.js b/editor/js/ui/view-navigator.js new file mode 100644 index 000000000..da1908982 --- /dev/null +++ b/editor/js/ui/view-navigator.js @@ -0,0 +1,164 @@ +/** + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + + RED.view.navigator = (function() { + + var nav_scale = 25; + var nav_width = 5000/nav_scale; + var nav_height = 5000/nav_scale; + + var navContainer; + var navBox; + var navBorder; + var navVis; + var scrollPos; + var scaleFactor; + var chartSize; + var dimensions; + var isDragging; + var isShowing = false; + + function refreshNodes() { + if (!isShowing) { + return; + } + var navNode = navVis.selectAll(".navnode").data(RED.view.getActiveNodes(),function(d){return d.id}); + navNode.exit().remove(); + navNode.enter().insert("rect") + .attr('class','navnode') + .attr("pointer-events", "none"); + navNode.each(function(d) { + d3.select(this).attr("x",function(d) { return (d.x-d.w/2)/nav_scale }) + .attr("y",function(d) { return (d.y-d.h/2)/nav_scale }) + .attr("width",function(d) { return Math.max(9,d.w/nav_scale) }) + .attr("height",function(d) { return Math.max(3,d.h/nav_scale) }) + .attr("fill",function(d) { return d._def.color;}) + }); + } + function onScroll() { + if (!isDragging) { + resizeNavBorder(); + } + } + function resizeNavBorder() { + if (navBorder) { + scaleFactor = RED.view.scale(); + chartSize = [ $("#chart").width(), $("#chart").height()]; + scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()]; + navBorder.attr('x',scrollPos[0]/nav_scale) + .attr('y',scrollPos[1]/nav_scale) + .attr('width',chartSize[0]/nav_scale/scaleFactor) + .attr('height',chartSize[1]/nav_scale/scaleFactor) + } + } + + return { + init: function() { + + $(window).resize(resizeNavBorder); + RED.events.on("sidebar:resize",resizeNavBorder); + + var hideTimeout; + + navContainer = $('
').css({ + "position":"absolute", + "bottom":$("#workspace-footer").height(), + "right":0, + zIndex: 1 + }).appendTo("#workspace").hide(); + + navBox = d3.select(navContainer[0]) + .append("svg:svg") + .attr("width", nav_width) + .attr("height", nav_height) + .attr("pointer-events", "all") + .style({ + position: "absolute", + bottom: 0, + right:0, + zIndex: 101, + "border-left": "1px solid #ccc", + "border-top": "1px solid #ccc", + background: "rgba(245,245,245,0.5)", + "box-shadow": "-1px 0 3px rgba(0,0,0,0.1)" + }); + + navBox.append("rect").attr("x",0).attr("y",0).attr("width",nav_width).attr("height",nav_height).style({ + fill:"none", + stroke:"none", + pointerEvents:"all" + }).on("mousedown", function() { + // Update these in case they have changed + scaleFactor = RED.view.scale(); + chartSize = [ $("#chart").width(), $("#chart").height()]; + dimensions = [chartSize[0]/nav_scale/scaleFactor, chartSize[1]/nav_scale/scaleFactor]; + var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]); + var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]); + navBorder.attr('x',newX).attr('y',newY); + isDragging = true; + $("#chart").scrollLeft(newX*nav_scale*scaleFactor); + $("#chart").scrollTop(newY*nav_scale*scaleFactor); + }).on("mousemove", function() { + if (!isDragging) { return } + if (d3.event.buttons === 0) { + isDragging = false; + return; + } + var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]); + var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]); + navBorder.attr('x',newX).attr('y',newY); + $("#chart").scrollLeft(newX*nav_scale*scaleFactor); + $("#chart").scrollTop(newY*nav_scale*scaleFactor); + }).on("mouseup", function() { + isDragging = false; + }) + + navBorder = navBox.append("rect") + .attr("stroke-dasharray","5,5") + .attr("pointer-events", "none") + .style({ + stroke: "#999", + strokeWidth: 1, + fill: "white", + }); + + navVis = navBox.append("svg:g") + + + $("#btn-navigate").click(function(evt) { + evt.preventDefault(); + if (!isShowing) { + isShowing = true; + $("#btn-navigate").addClass("selected"); + resizeNavBorder(); + refreshNodes(); + $("#chart").on("scroll",onScroll); + navContainer.fadeIn(200); + } else { + isShowing = false; + navContainer.fadeOut(100); + $("#chart").off("scroll",onScroll); + $("#btn-navigate").removeClass("selected"); + } + }) + }, + refresh: refreshNodes, + resize: resizeNavBorder + } + + +})(); diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index d6c1bfa8c..6a8dd77c4 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -332,6 +332,8 @@ RED.view = (function() { redraw(); }); + RED.view.navigator.init(); + $("#btn-zoom-out").click(function() {zoomOut();}); $("#btn-zoom-zero").click(function() {zoomZero();}); $("#btn-zoom-in").click(function() {zoomIn();}); @@ -1060,17 +1062,20 @@ RED.view = (function() { function zoomIn() { if (scaleFactor < 2) { scaleFactor += 0.1; + RED.view.navigator.resize(); redraw(); } } function zoomOut() { if (scaleFactor > 0.3) { scaleFactor -= 0.1; + RED.view.navigator.resize(); redraw(); } } function zoomZero() { scaleFactor = 1; + RED.view.navigator.resize(); redraw(); } @@ -2542,7 +2547,7 @@ RED.view = (function() { } ).classed("link_selected", false); } - + RED.view.navigator.refresh(); if (d3.event) { d3.event.preventDefault(); } @@ -2816,7 +2821,9 @@ RED.view = (function() { gridSize = Math.max(5,v); updateGrid(); } + }, + getActiveNodes: function() { + return activeNodes; } - }; })(); diff --git a/editor/sass/mixins.scss b/editor/sass/mixins.scss index 7201ef544..36d08c948 100644 --- a/editor/sass/mixins.scss +++ b/editor/sass/mixins.scss @@ -203,7 +203,7 @@ height: 25px; line-height: 23px; padding: 0 10px; - + user-select: none; .button-group:not(:last-child) { margin-right: 5px; @@ -227,6 +227,7 @@ font-size: 11px; line-height: 17px; height: 18px; + width: 18px; &.text-button { width: auto; padding: 0 5px; diff --git a/editor/sass/workspace.scss b/editor/sass/workspace.scss index 59b8fcf75..14b6eebb0 100644 --- a/editor/sass/workspace.scss +++ b/editor/sass/workspace.scss @@ -47,7 +47,9 @@ .workspace-footer-button { @include component-footer-button; } - +.workspace-footer-button-toggle { + @include component-footer-button-toggle; +} #workspace-footer { @include component-footer; } diff --git a/editor/templates/index.mst b/editor/templates/index.mst index b8d0ac0c0..972258a55 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -51,6 +51,7 @@ +