diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 19a506215..55ed68de8 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -1415,6 +1415,7 @@ Blockly.WorkspaceSvg.prototype.zoom = function(x, y, amount) { .translate(x * (1 - scaleChange), y * (1 - scaleChange)) .scale(scaleChange); // newScale and matrix.a should be identical (within a rounding error). + // ScrollX and scrollY are in pixels. this.scrollX = matrix.e - metrics.absoluteLeft; this.scrollY = matrix.f - metrics.absoluteTop; } @@ -1504,6 +1505,118 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { } }; +/** + * Get the dimensions of the given workspace component, in pixels. + * @param {Blockly.Toolbox|Blockly.Flyout} elem The element to get the + * dimensions of, or null. It should be a toolbox or flyout, and should + * implement getWidth() and getHeight(). + * @return {!Object} An object containing width and height attributes, which + * will both be zero if elem did not exist. + * @private + */ +Blockly.WorkspaceSvg.getDimensionsPx_ = function(elem) { + var width = 0; + var height = 0; + if (elem) { + width = elem.getWidth(); + height = elem.getHeight(); + } + return { + width: width, + height: height + }; +}; + +/** + * Get the content dimensions of the given workspace, taking into account + * whether or not it is scrollable and what size the workspace div is on screen. + * @param {!Blockly.WorkspaceSvg} ws The workspace to measure. + * @param {!Object} svgSize An object containing height and width attributes in + * CSS pixels. Together they specify the size of the visible workspace, not + * including areas covered up by the toolbox. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing at least + * - height and width in pixels + * - left and top in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensions_ = function(ws, svgSize) { + if (ws.scrollbar) { + return Blockly.WorkspaceSvg.getContentDimensionsBounded_(ws, svgSize); + } else { + return Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + } +}; + +/** + * Get the bounding box for all workspace contents, in pixels. + * @param {!Blockly.WorkspaceSvg} ws The workspace to inspect. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing + * - height and width in pixels + * - left, right, top and bottom in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensionsExact_ = function(ws) { + // Block bounding box is in workspace coordinates. + var blockBox = ws.getBlocksBoundingBox(); + var scale = ws.scale; + + // Convert to pixels. + var width = blockBox.width * scale; + var height = blockBox.height * scale; + var left = blockBox.x * scale; + var top = blockBox.y * scale; + + return { + left: left, + top: top, + right: left + width, + bottom: top + height, + width: width, + height: height + }; +}; + +/** + * Calculate the size of a scrollable workspace, which should include room for a + * half screen border around the workspace contents. + * @param {!Blockly.WorkspaceSvg} ws The workspace to measure. + * @param {!Object} svgSize An object containing height and width attributes in + * CSS pixels. Together they specify the size of the visible workspace, not + * including areas covered up by the toolbox. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing + * - height and width in pixels + * - left and top in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensionsBounded_ = function(ws, svgSize) { + var content = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + + // View height and width are both in pixels, and are the same as the svg size. + var viewWidth = svgSize.width; + var viewHeight = svgSize.height; + var halfWidth = viewWidth / 2; + var halfHeight = viewHeight / 2; + + // Add a border around the content that is at least half a screenful wide. + // Ensure border is wide enough that blocks can scroll over entire screen. + var left = Math.min(content.left - halfWidth, content.right - viewWidth); + var right = Math.max(content.right + halfWidth, content.left + viewWidth); + + var top = Math.min(content.top - halfHeight, content.bottom - viewHeight); + var bottom = Math.max(content.bottom + halfHeight, content.top + viewHeight); + + var dimensions = { + left: left, + top: top, + height: bottom - top, + width: right - left + }; + return dimensions; +}; + /** * Return an object with all the metrics required to size scrollbars for a * top level workspace. The following properties are computed: @@ -1529,70 +1642,59 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { * @this Blockly.WorkspaceSvg */ Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() { + + var toolboxDimensions = + Blockly.WorkspaceSvg.getDimensionsPx_(this.toolbox_); + var flyoutDimensions = + Blockly.WorkspaceSvg.getDimensionsPx_(this.flyout_); + + // Contains height and width in CSS pixels. + // svgSize is equivalent to the size of the injectionDiv at this point. var svgSize = Blockly.svgSize(this.getParentSvg()); if (this.toolbox_) { if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP || this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { - svgSize.height -= this.toolbox_.getHeight(); + svgSize.height -= toolboxDimensions.height; } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT || this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { - svgSize.width -= this.toolbox_.getWidth(); + svgSize.width -= toolboxDimensions.width; } } - // Set the margin to match the flyout's margin so that the workspace does - // not jump as blocks are added. - var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1; - var viewWidth = svgSize.width - MARGIN; - var viewHeight = svgSize.height - MARGIN; - var blockBox = this.getBlocksBoundingBox(); + // svgSize is now the space taken up by the Blockly workspace, not including + // the toolbox. + var contentDimensions = + Blockly.WorkspaceSvg.getContentDimensions_(this, svgSize); - // Fix scale. - var contentWidth = blockBox.width * this.scale; - var contentHeight = blockBox.height * this.scale; - var contentX = blockBox.x * this.scale; - var contentY = blockBox.y * this.scale; - if (this.scrollbar) { - // Add a border around the content that is at least half a screenful wide. - // Ensure border is wide enough that blocks can scroll over entire screen. - var leftEdge = Math.min(contentX - viewWidth / 2, - contentX + contentWidth - viewWidth); - var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2, - contentX + viewWidth); - var topEdge = Math.min(contentY - viewHeight / 2, - contentY + contentHeight - viewHeight); - var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2, - contentY + viewHeight); - } else { - var leftEdge = blockBox.x; - var rightEdge = leftEdge + blockBox.width; - var topEdge = blockBox.y; - var bottomEdge = topEdge + blockBox.height; - } var absoluteLeft = 0; if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { - absoluteLeft = this.toolbox_.getWidth(); + absoluteLeft = toolboxDimensions.width; } var absoluteTop = 0; if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { - absoluteTop = this.toolbox_.getHeight(); + absoluteTop = toolboxDimensions.height; } var metrics = { + contentHeight: contentDimensions.height, + contentWidth: contentDimensions.width, + contentTop: contentDimensions.top, + contentLeft: contentDimensions.left, + viewHeight: svgSize.height, viewWidth: svgSize.width, - contentHeight: bottomEdge - topEdge, - contentWidth: rightEdge - leftEdge, - viewTop: -this.scrollY, - viewLeft: -this.scrollX, - contentTop: topEdge, - contentLeft: leftEdge, + viewTop: -this.scrollY, // Must be in pixels, somehow. + viewLeft: -this.scrollX, // Must be in pixels, somehow. + absoluteTop: absoluteTop, absoluteLeft: absoluteLeft, - toolboxWidth: this.toolbox_ ? this.toolbox_.getWidth() : 0, - toolboxHeight: this.toolbox_ ? this.toolbox_.getHeight() : 0, - flyoutWidth: this.flyout_ ? this.flyout_.getWidth() : 0, - flyoutHeight: this.flyout_ ? this.flyout_.getHeight() : 0, + + toolboxWidth: toolboxDimensions.width, + toolboxHeight: toolboxDimensions.height, + + flyoutWidth: flyoutDimensions.width, + flyoutHeight: flyoutDimensions.height, + toolboxPosition: this.toolboxPosition }; return metrics;