From 213469a479a92f1a3ce043803ad560a9b2a3f0cf Mon Sep 17 00:00:00 2001 From: picklesrus Date: Fri, 3 Jun 2016 16:11:55 -0700 Subject: [PATCH] Change the blockly workspace resizing strategy. (#386) * Add a new method to be called when the contents of the workspace change and the scrollbars need to be adjusted but the the chrome (trash, toolbox, etc) are expected to stay in the same place. Change a bunch of calls to svgResize to either be removed or call the new method instead. This is a nice performance win since the offsetHeight/Width call in svgResize can be expensive, especially when called as often as we do - there was some layout thrashing. This also paves the way for moving calls to recordDeleteAreas (which is also expensive) to a more cacheable spot than on every mouse down/touch event. of things (namely the scrollbars) * Fix size of graph demo when it first loads by calling svgResize. The graph starts with fixed width and was relying on a resize event to fire (which I believe was removed in commit 217c681b86b0f2df76c479c9efae62e6e). * Fix the resizing of the code demo. The demo's tab min-width used to match the toolbox's width was only being set on a resize event, but commit 217c681b86b0f2df76c479c9efae62e6e changed how that worked. * Fix up some comments. * Use specific workspaces rather than Blockly.getMainWorkspace(). * Make workspace required for resizeSvgContents and update some calls to send real workspaces rather than ones that are null. Remove the private tag on terminateDrag_ because it is only actually called from outside the BlockSvg object. * Remove a rogue period. * Recategorize BlockSvg.terminateDrag_ to @package instead of @private so that other developers don't use it, but it still can be used by other Blockly classes. * Add a TODO to fix issue #307. * Add @package to workspace resizeContents. --- core/block_render_svg.js | 2 +- core/block_svg.js | 13 +++++++------ core/blockly.js | 32 ++++++++++---------------------- core/flyout.js | 9 +++++---- core/inject.js | 6 ++++-- core/toolbox.js | 6 ++++-- core/workspace_svg.js | 24 +++++++++++++++++++++--- core/xml.js | 5 +++-- demos/code/code.js | 5 +++-- demos/graph/index.html | 2 ++ 10 files changed, 60 insertions(+), 44 deletions(-) diff --git a/core/block_render_svg.js b/core/block_render_svg.js index 017bc3402..2a8ee7443 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -288,7 +288,7 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) { parentBlock.render(true); } else { // Top-most block. Fire an event to allow scrollbars to resize. - Blockly.asyncSvgResize(this.workspace); + Blockly.resizeSvgContents(this.workspace); } } Blockly.Field.stopCache(); diff --git a/core/block_svg.js b/core/block_svg.js index ef39321dc..c84220606 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -232,9 +232,9 @@ Blockly.BlockSvg.onMouseMoveWrapper_ = null; /** * Stop binding to the global mouseup and mousemove events. - * @private + * @package */ -Blockly.BlockSvg.terminateDrag_ = function() { +Blockly.BlockSvg.terminateDrag = function() { Blockly.BlockSvg.disconnectUiStop_(); if (Blockly.BlockSvg.onMouseUpWrapper_) { Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_); @@ -273,7 +273,7 @@ Blockly.BlockSvg.terminateDrag_ = function() { Blockly.Events.setGroup(false); }, Blockly.BUMP_DELAY); // Fire an event to allow scrollbars to resize. - Blockly.asyncSvgResize(this.workspace); + Blockly.resizeSvgContents(selected.workspace); } } Blockly.dragMode_ = Blockly.DRAG_NONE; @@ -531,8 +531,6 @@ Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { return; } this.workspace.markFocused(); - // Update Blockly's knowledge of its own location. - Blockly.svgResize(this.workspace); Blockly.terminateDrag_(); this.select(); Blockly.hideChaff(); @@ -611,11 +609,14 @@ Blockly.BlockSvg.prototype.onMouseUp_ = function(e) { if (trashcan) { goog.Timer.callOnce(trashcan.close, 100, trashcan); } + // Save the block's workspace temporarily so we can resize the + // contents once the block is disposed. + var selectedWorkspace = Blockly.selected.workspace; Blockly.selected.dispose(false, true); // Dropping a block on the trash can will usually cause the workspace to // resize to contain the newly positioned block. Force a second resize // now that the block has been deleted. - Blockly.asyncSvgResize(this.workspace); + Blockly.resizeSvgContents(selectedWorkspace); } if (Blockly.highlightedConnection_) { Blockly.highlightedConnection_.unhighlight(); diff --git a/core/blockly.js b/core/blockly.js index 3e9881e04..4fd5de0ca 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -141,36 +141,24 @@ Blockly.svgSize = function(svg) { }; /** - * Schedule a call to the resize handler. Groups of simultaneous events (e.g. - * a tree of blocks being deleted) are merged into one call. - * @param {Blockly.WorkspaceSvg} workspace Any workspace in the SVG. + * Size the workspace when the contents change. This also updates + * scrollbars accordingly. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize. */ -Blockly.asyncSvgResize = function(workspace) { - if (Blockly.svgResizePending_) { - return; - } - if (!workspace) { - workspace = Blockly.getMainWorkspace(); - } - Blockly.svgResizePending_ = true; - setTimeout(function() {Blockly.svgResize(workspace);}, 0); +Blockly.resizeSvgContents = function(workspace) { + workspace.resizeContents(); }; -/** - * Flag indicating a resize event is scheduled. - * Used to fire only one resize after multiple changes. - * @type {boolean} - * @private - */ -Blockly.svgResizePending_ = false; /** - * Size the SVG image to completely fill its container. + * Size the SVG image to completely fill its container. Call this when the view + * actually changes sizes (e.g. on a window resize/device orientation change). + * See Blockly.resizeSvgContents to resize the workspace when the contents + * change (e.g. when a block is added or removed). * Record the height/width of the SVG image. * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG. */ Blockly.svgResize = function(workspace) { - Blockly.svgResizePending_ = false; var mainWorkspace = workspace; while (mainWorkspace.options.parentWorkspace) { mainWorkspace = mainWorkspace.options.parentWorkspace; @@ -316,7 +304,7 @@ Blockly.onKeyDown_ = function(e) { * @private */ Blockly.terminateDrag_ = function() { - Blockly.BlockSvg.terminateDrag_(); + Blockly.BlockSvg.terminateDrag(); Blockly.Flyout.terminateDrag_(); }; diff --git a/core/flyout.js b/core/flyout.js index 5dce8223f..3caa8deaa 100644 --- a/core/flyout.js +++ b/core/flyout.js @@ -591,8 +591,9 @@ Blockly.Flyout.prototype.show = function(xmlList) { this.offsetHorizontalRtlBlocks(this.workspace_.getTopBlocks(false)); this.filterForCapacity_(); - // Fire a resize event to update the flyout's scrollbar. - Blockly.svgResize(this.workspace_); + // Correctly position the flyout's scrollbar when it opens. + this.position(); + this.reflowWrapper_ = this.reflow.bind(this); this.workspace_.addChangeListener(this.reflowWrapper_); }; @@ -1039,7 +1040,7 @@ Blockly.Flyout.prototype.reflowHorizontal = function(blocks) { } // Record the height for .getMetrics_ and .position. this.height_ = flyoutHeight; - Blockly.asyncSvgResize(this.workspace_); + Blockly.resizeSvgContents(this.workspace_); } }; @@ -1092,7 +1093,7 @@ Blockly.Flyout.prototype.reflowVertical = function(blocks) { } // Record the width for .getMetrics_ and .position. this.width_ = flyoutWidth; - Blockly.asyncSvgResize(this.workspace_); + Blockly.resizeSvgContents(this.workspace_); } }; diff --git a/core/inject.js b/core/inject.js index 17bfccc0b..d8e6e3d20 100644 --- a/core/inject.js +++ b/core/inject.js @@ -56,6 +56,7 @@ Blockly.inject = function(container, opt_options) { Blockly.init_(workspace); workspace.markFocused(); Blockly.bindEvent_(svg, 'focus', workspace, workspace.markFocused); + Blockly.svgResize(workspace); return workspace; }; @@ -266,7 +267,7 @@ Blockly.init_ = function(mainWorkspace) { Blockly.bindEvent_(window, 'resize', null, function() { Blockly.hideChaff(true); - Blockly.asyncSvgResize(mainWorkspace); + Blockly.svgResize(mainWorkspace); }); Blockly.inject.bindDocumentEvents_(); @@ -322,7 +323,8 @@ Blockly.inject.bindDocumentEvents_ = function() { // Some iPad versions don't fire resize after portrait to landscape change. if (goog.userAgent.IPAD) { Blockly.bindEvent_(window, 'orientationchange', document, function() { - Blockly.asyncSvgResize(); + // TODO(#397): Fix for multiple blockly workspaces. + Blockly.svgResize(Blockly.getMainWorkspace()); }); } } diff --git a/core/toolbox.js b/core/toolbox.js index 2eb9cbb54..d9ca56dcc 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -348,7 +348,7 @@ Blockly.Toolbox.prototype.populate_ = function(newTree) { } // Fire a resize event since the toolbox may have changed width and height. - Blockly.asyncSvgResize(this.workspace_); + Blockly.resizeSvgContents(this.workspace_); }; /** @@ -533,7 +533,9 @@ Blockly.Toolbox.TreeNode = function(toolbox, html, opt_config, opt_domHelper) { goog.ui.tree.TreeNode.call(this, html, opt_config, opt_domHelper); if (toolbox) { var resize = function() { - Blockly.asyncSvgResize(toolbox.workspace_); + // Even though the div hasn't changed size, the visible workspace + // surface of the workspace has, so we may need to reposition everything. + Blockly.svgResize(toolbox.workspace_); }; // Fire a resize event since the toolbox may have changed width. goog.events.listen(toolbox.tree_, diff --git a/core/workspace_svg.js b/core/workspace_svg.js index a1788868e..2a88ec761 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -300,7 +300,26 @@ Blockly.WorkspaceSvg.prototype.addFlyout_ = function() { }; /** - * Resize this workspace and its containing objects. + * Resize the parts of the workspace that change when the workspace + * contents (e.g. block positions) change. This will also scroll the + * workspace contents if needed. + * @package + */ +Blockly.WorkspaceSvg.prototype.resizeContents = function() { + if (this.scrollbar) { + // TODO(picklesrus): Once rachel-fenichel's scrollbar refactoring + // is complete, call the method that only resizes scrollbar + // based on contents. + this.scrollbar.resize(); + } +}; + +/** + * Resize and reposition all of the workspace chrome (toolbox, + * trash, scrollbars etc.) + * This should be called when something changes that + * requires recalculating dimensions and positions of the + * trash, zoom, toolbox, etc. (e.g. window resize). */ Blockly.WorkspaceSvg.prototype.resize = function() { if (this.toolbox_) { @@ -576,7 +595,6 @@ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) { if (Blockly.isTargetInput_(e)) { return; } - Blockly.svgResize(this); Blockly.terminateDrag_(); // In case mouse-up event was lost. Blockly.hideChaff(); var isTargetWorkspace = e.target && e.target.nodeName && @@ -716,7 +734,7 @@ Blockly.WorkspaceSvg.prototype.cleanUp_ = function() { } Blockly.Events.setGroup(false); // Fire an event to allow scrollbars to resize. - Blockly.asyncSvgResize(this); + Blockly.resizeSvgContents(this); }; /** diff --git a/core/xml.js b/core/xml.js index 48cef7216..3f4652bf8 100644 --- a/core/xml.js +++ b/core/xml.js @@ -351,8 +351,9 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) { } }, 1); topBlock.updateDisabled(); - // Fire an event to allow scrollbars to resize. - Blockly.asyncSvgResize(workspace); + // Allow the scrollbars to resize and move based on the new contents. + // TODO(@picklesrus): #387. Remove when domToBlock avoids resizing. + Blockly.resizeSvgContents(workspace); } Blockly.Events.enable(); if (Blockly.Events.isEnabled()) { diff --git a/demos/code/code.js b/demos/code/code.js index e75b452d4..a55633754 100644 --- a/demos/code/code.js +++ b/demos/code/code.js @@ -294,7 +294,7 @@ Code.tabClick = function(clickedName) { if (clickedName == 'blocks') { Code.workspace.setVisible(true); } - Blockly.asyncSvgResize(this.workspace_); + Blockly.svgResize(Code.workspace); }; /** @@ -380,7 +380,6 @@ Code.init = function() { // Account for the 19 pixel margin and on each side. } }; - onresize(); window.addEventListener('resize', onresize, false); var toolbox = document.getElementById('toolbox'); @@ -432,6 +431,8 @@ Code.init = function() { Code.bindClick('tab_' + name, function(name_) {return function() {Code.tabClick(name_);};}(name)); } + onresize(); + Blockly.svgResize(Code.workspace); // Lazy-load the syntax-highlighting. window.setTimeout(Code.importPrettify, 1); diff --git a/demos/graph/index.html b/demos/graph/index.html index 7e9daf2dd..ce720ccde 100644 --- a/demos/graph/index.html +++ b/demos/graph/index.html @@ -328,6 +328,7 @@ Graph.plot = function(code) { Graph.resize = function() { var width = Math.max(window.innerWidth - 440, 250); document.getElementById('blocklyDiv').style.width = width + 'px'; + Blockly.svgResize(Graph.workspace); }; /** @@ -344,6 +345,7 @@ Graph.init = function() { // When Blockly changes, update the graph. Graph.workspace.addChangeListener(Graph.drawVisualization); + Graph.resize(); }; window.addEventListener('load', Graph.init);