From 869e4eb3660e9979de483f783319cb36c3fafac2 Mon Sep 17 00:00:00 2001 From: alschmiedt Date: Tue, 9 Mar 2021 17:09:49 -0800 Subject: [PATCH] Update flyout metrics to work with the new metrics manager (#4684) --- core/flyout_base.js | 19 ++---- core/flyout_horizontal.js | 115 +++++++++-------------------------- core/flyout_vertical.js | 113 +++++++++------------------------- core/metrics_manager.js | 120 ++++++++++++++++++++++++++++++++++++- core/mutator.js | 4 +- core/utils/metrics.js | 14 ++--- core/workspace_svg.js | 10 ++++ tests/mocha/flyout_test.js | 63 ++++++++++++------- 8 files changed, 239 insertions(+), 219 deletions(-) diff --git a/core/flyout_base.js b/core/flyout_base.js index 4aca6b9f2..1e3bdad0b 100644 --- a/core/flyout_base.js +++ b/core/flyout_base.js @@ -21,6 +21,7 @@ goog.require('Blockly.Events'); goog.require('Blockly.Events.BlockCreate'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.VarCreate'); +goog.require('Blockly.FlyoutMetricsManager'); /** @suppress {extraRequire} */ goog.require('Blockly.Gesture'); goog.require('Blockly.ScrollbarPair'); @@ -41,7 +42,6 @@ goog.requireType('Blockly.FlyoutButton'); goog.requireType('Blockly.IDeleteArea'); goog.requireType('Blockly.IFlyout'); goog.requireType('Blockly.Options'); -goog.requireType('Blockly.utils.Metrics'); goog.requireType('Blockly.utils.Rect'); @@ -55,9 +55,6 @@ goog.requireType('Blockly.utils.Rect'); * @implements {Blockly.IFlyout} */ Blockly.Flyout = function(workspaceOptions) { - workspaceOptions.getMetrics = - /** @type {function():!Blockly.utils.Metrics} */ ( - this.getMetrics_.bind(this)); workspaceOptions.setMetrics = this.setMetrics_.bind(this); /** @@ -65,6 +62,9 @@ Blockly.Flyout = function(workspaceOptions) { * @protected */ this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); + this.workspace_.setMetricsManager( + new Blockly.FlyoutMetricsManager(this.workspace_, this)); + this.workspace_.isFlyout = true; // Keep the workspace visibility consistent with the flyout's visibility. this.workspace_.setVisible(this.isVisible_); @@ -425,6 +425,8 @@ Blockly.Flyout.prototype.updateDisplay_ = function() { Blockly.Flyout.prototype.positionAt_ = function(width, height, x, y) { this.svgGroup_.setAttribute("width", width); this.svgGroup_.setAttribute("height", height); + this.workspace_.setCachedParentSvgSize(width, height); + if (this.svgGroup_.tagName == 'svg') { var transform = 'translate(' + x + 'px,' + y + 'px)'; Blockly.utils.dom.setCssTransform(this.svgGroup_, transform); @@ -1038,15 +1040,6 @@ Blockly.Flyout.prototype.position; */ Blockly.Flyout.prototype.isDragTowardWorkspace; -/** - * Return an object with all the metrics required to size scrollbars for the - * flyout. - * @return {Blockly.utils.Metrics} Contains size and position metrics of the - * flyout. - * @protected - */ -Blockly.Flyout.prototype.getMetrics_; - /** * Sets the translation of the flyout to match the scrollbars. * @param {!{x:number,y:number}} xyRatio Contains a y property which is a float diff --git a/core/flyout_horizontal.js b/core/flyout_horizontal.js index a4e88224e..acdbc7479 100644 --- a/core/flyout_horizontal.js +++ b/core/flyout_horizontal.js @@ -27,7 +27,6 @@ goog.require('Blockly.WidgetDiv'); goog.requireType('Blockly.Options'); goog.requireType('Blockly.utils.Coordinate'); -goog.requireType('Blockly.utils.Metrics'); /** @@ -43,73 +42,6 @@ Blockly.HorizontalFlyout = function(workspaceOptions) { }; Blockly.utils.object.inherits(Blockly.HorizontalFlyout, Blockly.Flyout); -/** - * Return an object with all the metrics required to size scrollbars for the - * flyout. The following properties are computed: - * .viewHeight: Height of the visible rectangle, - * .viewWidth: Width of the visible rectangle, - * .contentHeight: Height of the contents, - * .contentWidth: Width of the contents, - * .scrollHeight: Height of the scroll area, - * .scrollWidth: Width of the scroll area, - * .viewTop: Offset of top edge of visible rectangle from parent, - * .contentTop: Offset of the top-most content from the y=0 coordinate, - * .scrollTop: Offset of the scroll area top from the y=0 coordinate, - * .absoluteTop: Top-edge of view. - * .viewLeft: Offset of the left edge of visible rectangle from parent, - * .contentLeft: Offset of the left-most content from the x=0 coordinate, - * .scrollLeft: Offset of the scroll area left from the x=0 coordinate, - * .absoluteLeft: Left-edge of view. - * @return {Blockly.utils.Metrics} Contains size and position metrics of the - * flyout. - * @protected - */ -Blockly.HorizontalFlyout.prototype.getMetrics_ = function() { - if (!this.isVisible()) { - // Flyout is hidden. - return null; - } - - try { - var optionBox = this.workspace_.getCanvas().getBBox(); - } catch (e) { - // Firefox has trouble with hidden elements (Bug 528969). - var optionBox = {height: 0, y: 0, width: 0, x: 0}; - } - - var absoluteTop = this.SCROLLBAR_PADDING; - var absoluteLeft = this.SCROLLBAR_PADDING; - if (this.toolboxPosition_ == Blockly.utils.toolbox.Position.BOTTOM) { - absoluteTop = 0; - } - var viewHeight = this.height_; - if (this.toolboxPosition_ == Blockly.utils.toolbox.Position.TOP) { - viewHeight -= this.SCROLLBAR_PADDING; - } - var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING; - - var metrics = { - contentHeight: optionBox.height * this.workspace_.scale, - contentWidth: optionBox.width * this.workspace_.scale, - contentTop: optionBox.y, - contentLeft: optionBox.x, - - scrollHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale, - scrollWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale, - scrollTop: optionBox.y - this.MARGIN, - scrollLeft: optionBox.x - this.MARGIN, - - viewHeight: viewHeight, - viewWidth: viewWidth, - viewTop: -this.workspace_.scrollY, - viewLeft: -this.workspace_.scrollX, - - absoluteTop: absoluteTop, - absoluteLeft: absoluteLeft - }; - return metrics; -}; - /** * Sets the translation of the flyout to match the scrollbars. * @param {!{x:number,y:number}} xyRatio Contains a y property which is a float @@ -118,20 +50,23 @@ Blockly.HorizontalFlyout.prototype.getMetrics_ = function() { * @protected */ Blockly.HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) { - var metrics = this.getMetrics_(); - // This is a fix to an apparent race condition. - if (!metrics) { + if (!this.isVisible()) { return; } + var metricsManager = this.workspace_.getMetricsManager(); + var scrollMetrics = metricsManager.getScrollMetrics(); + var viewMetrics = metricsManager.getViewMetrics(); + var absoluteMetrics = metricsManager.getAbsoluteMetrics(); + if (typeof xyRatio.x == 'number') { this.workspace_.scrollX = - -(metrics.scrollLeft + - (metrics.scrollWidth - metrics.viewWidth) * xyRatio.x); + -(scrollMetrics.left + + (scrollMetrics.width - viewMetrics.width) * xyRatio.x); } - this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, - this.workspace_.scrollY + metrics.absoluteTop); + this.workspace_.translate(this.workspace_.scrollX + absoluteMetrics.left, + this.workspace_.scrollY + absoluteMetrics.top); }; /** @@ -148,22 +83,24 @@ Blockly.HorizontalFlyout.prototype.getX = function() { * @return {number} Y coordinate. */ Blockly.HorizontalFlyout.prototype.getY = function() { - var targetWorkspaceMetrics = this.targetWorkspace.getMetrics(); - if (!targetWorkspaceMetrics) { - // Hidden components will return null. + if (!this.isVisible()) { return 0; } + var metricsManager = this.targetWorkspace.getMetricsManager(); + var absoluteMetrics = metricsManager.getAbsoluteMetrics(); + var viewMetrics = metricsManager.getViewMetrics(); + var toolboxMetrics = metricsManager.getToolboxMetrics(); var y = 0; var atTop = this.toolboxPosition_ == Blockly.utils.toolbox.Position.TOP; // If this flyout is not the trashcan flyout (e.g. toolbox or mutator). if (this.targetWorkspace.toolboxPosition == this.toolboxPosition_) { // If there is a category toolbox. - if (targetWorkspaceMetrics.toolboxHeight) { + if (toolboxMetrics.height) { if (atTop) { - y = targetWorkspaceMetrics.toolboxHeight; + y = toolboxMetrics.height; } else { - y = targetWorkspaceMetrics.viewHeight - this.height_; + y = viewMetrics.height - this.height_; } // Simple (flyout-only) toolbox. } else { @@ -171,7 +108,7 @@ Blockly.HorizontalFlyout.prototype.getY = function() { y = 0; } else { // The simple flyout does not cover the workspace. - y = targetWorkspaceMetrics.viewHeight; + y = viewMetrics.height; } } // Trashcan flyout is opposite the main flyout. @@ -183,8 +120,7 @@ Blockly.HorizontalFlyout.prototype.getY = function() { // to align the bottom edge of the flyout with the bottom edge of the // blocklyDiv, we calculate the full height of the div minus the height // of the flyout. - y = targetWorkspaceMetrics.viewHeight + - targetWorkspaceMetrics.absoluteTop - this.height_; + y = viewMetrics.height + absoluteMetrics.top - this.height_; } } @@ -201,7 +137,7 @@ Blockly.HorizontalFlyout.prototype.position = function() { var metricsManager = this.targetWorkspace.getMetricsManager(); var targetWorkspaceViewMetrics = metricsManager.getViewMetrics(); - // Record the width for Blockly.Flyout.getMetrics_. + // Record the width for workspace metrics. this.width_ = targetWorkspaceViewMetrics.width; var edgeWidth = targetWorkspaceViewMetrics.width - 2 * this.CORNER_RADIUS; @@ -275,8 +211,11 @@ Blockly.HorizontalFlyout.prototype.wheel_ = function(e) { var delta = scrollDelta.x || scrollDelta.y; if (delta) { - var metrics = this.getMetrics_(); - var pos = (metrics.viewLeft - metrics.scrollLeft) + delta; + var metricsManager = this.workspace_.getMetricsManager(); + var scrollMetrics = metricsManager.getScrollMetrics(); + var viewMetrics = metricsManager.getViewMetrics(); + + var pos = (viewMetrics.left - scrollMetrics.left) + delta; this.workspace_.scrollbar.setX(pos); // When the flyout moves from a wheel event, hide WidgetDiv and DropDownDiv. Blockly.WidgetDiv.hide(); @@ -420,7 +359,7 @@ Blockly.HorizontalFlyout.prototype.reflowInternal_ = function() { this.targetWorkspace.scrollX, this.targetWorkspace.scrollY + flyoutHeight); } - // Record the height for .getMetrics_ and .position. + // Record the height for workspace metrics and .position. this.height_ = flyoutHeight; this.position(); } diff --git a/core/flyout_vertical.js b/core/flyout_vertical.js index b20427599..399fd2b5a 100644 --- a/core/flyout_vertical.js +++ b/core/flyout_vertical.js @@ -27,7 +27,6 @@ goog.require('Blockly.WidgetDiv'); goog.requireType('Blockly.Options'); goog.requireType('Blockly.utils.Coordinate'); -goog.requireType('Blockly.utils.Metrics'); /** @@ -48,70 +47,6 @@ Blockly.utils.object.inherits(Blockly.VerticalFlyout, Blockly.Flyout); */ Blockly.VerticalFlyout.registryName = 'verticalFlyout'; -/** - * Return an object with all the metrics required to size scrollbars for the - * flyout. The following properties are computed: - * .viewHeight: Height of the visible rectangle, - * .viewWidth: Width of the visible rectangle, - * .contentHeight: Height of the contents, - * .contentWidth: Width of the contents, - * .viewTop: Offset of top edge of visible rectangle from parent, - * .contentTop: Offset of the top-most content from the y=0 coordinate, - * .scrollTop: Offset of the scroll area top from the y=0 coordinate, - * .absoluteTop: Top-edge of view. - * .viewLeft: Offset of the left edge of visible rectangle from parent, - * .contentLeft: Offset of the left-most content from the x=0 coordinate, - * .scrollLeft: Offset of the scroll area left from the x=0 coordinate, - * .absoluteLeft: Left-edge of view. - * @return {Blockly.utils.Metrics} Contains size and position metrics of the - * flyout. - * @protected - */ -Blockly.VerticalFlyout.prototype.getMetrics_ = function() { - if (!this.isVisible()) { - // Flyout is hidden. - return null; - } - - try { - var optionBox = this.workspace_.getCanvas().getBBox(); - } catch (e) { - // Firefox has trouble with hidden elements (Bug 528969). - var optionBox = {height: 0, y: 0, width: 0, x: 0}; - } - - // Padding for the end of the scrollbar. - var absoluteTop = this.SCROLLBAR_PADDING; - var absoluteLeft = 0; - - var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING; - var viewWidth = this.width_; - if (!this.RTL) { - viewWidth -= this.SCROLLBAR_PADDING; - } - - var metrics = { - contentHeight: optionBox.height * this.workspace_.scale, - contentWidth: optionBox.width * this.workspace_.scale, - contentTop: optionBox.y, - contentLeft: optionBox.x, - - scrollHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale, - scrollWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale, - scrollTop: optionBox.y - this.MARGIN, - scrollLeft: optionBox.x - this.MARGIN, - - viewHeight: viewHeight, - viewWidth: viewWidth, - viewTop: -this.workspace_.scrollY, - viewLeft: -this.workspace_.scrollX, - - absoluteTop: absoluteTop, - absoluteLeft: absoluteLeft - }; - return metrics; -}; - /** * Sets the translation of the flyout to match the scrollbars. * @param {!{x:number,y:number}} xyRatio Contains a y property which is a float @@ -120,18 +55,21 @@ Blockly.VerticalFlyout.prototype.getMetrics_ = function() { * @protected */ Blockly.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) { - var metrics = this.getMetrics_(); - // This is a fix to an apparent race condition. - if (!metrics) { + if (!this.isVisible()) { return; } + var metricsManager = this.workspace_.getMetricsManager(); + var scrollMetrics = metricsManager.getScrollMetrics(); + var viewMetrics = metricsManager.getViewMetrics(); + var absoluteMetrics = metricsManager.getAbsoluteMetrics(); + if (typeof xyRatio.y == 'number') { this.workspace_.scrollY = - -(metrics.scrollTop + - (metrics.scrollHeight - metrics.viewHeight) * xyRatio.y); + -(scrollMetrics.top + + (scrollMetrics.height - viewMetrics.height) * xyRatio.y); } - this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, - this.workspace_.scrollY + metrics.absoluteTop); + this.workspace_.translate(this.workspace_.scrollX + absoluteMetrics.left, + this.workspace_.scrollY + absoluteMetrics.top); }; /** @@ -139,22 +77,23 @@ Blockly.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) { * @return {number} X coordinate. */ Blockly.VerticalFlyout.prototype.getX = function() { - var targetWorkspaceMetrics = this.targetWorkspace.getMetrics(); - if (!targetWorkspaceMetrics) { - // Hidden components will return null. + if (!this.isVisible()) { return 0; } - + var metricsManager = this.targetWorkspace.getMetricsManager(); + var absoluteMetrics = metricsManager.getAbsoluteMetrics(); + var viewMetrics = metricsManager.getViewMetrics(); + var toolboxMetrics = metricsManager.getToolboxMetrics(); var x = 0; // If this flyout is not the trashcan flyout (e.g. toolbox or mutator). if (this.targetWorkspace.toolboxPosition == this.toolboxPosition_) { // If there is a category toolbox. - if (targetWorkspaceMetrics.toolboxWidth) { + if (toolboxMetrics.width) { if (this.toolboxPosition_ == Blockly.utils.toolbox.Position.LEFT) { - x = targetWorkspaceMetrics.toolboxWidth; + x = toolboxMetrics.width; } else { - x = targetWorkspaceMetrics.viewWidth - this.width_; + x = viewMetrics.width - this.width_; } // Simple (flyout-only) toolbox. } else { @@ -162,7 +101,7 @@ Blockly.VerticalFlyout.prototype.getX = function() { x = 0; } else { // The simple flyout does not cover the workspace. - x = targetWorkspaceMetrics.viewWidth; + x = viewMetrics.width; } } // Trashcan flyout is opposite the main flyout. @@ -174,8 +113,7 @@ Blockly.VerticalFlyout.prototype.getX = function() { // to align the right edge of the flyout with the right edge of the // blocklyDiv, we calculate the full width of the div minus the width // of the flyout. - x = targetWorkspaceMetrics.viewWidth + - targetWorkspaceMetrics.absoluteLeft - this.width_; + x = viewMetrics.width + absoluteMetrics.left - this.width_; } } @@ -201,7 +139,7 @@ Blockly.VerticalFlyout.prototype.position = function() { var metricsManager = this.targetWorkspace.getMetricsManager(); var targetWorkspaceViewMetrics = metricsManager.getViewMetrics(); - // Record the height for Blockly.Flyout.getMetrics_ + // Record the height for workspace metrics. this.height_ = targetWorkspaceViewMetrics.height; var edgeWidth = this.width_ - this.CORNER_RADIUS; @@ -264,8 +202,11 @@ Blockly.VerticalFlyout.prototype.wheel_ = function(e) { var scrollDelta = Blockly.utils.getScrollDeltaPixels(e); if (scrollDelta.y) { - var metrics = this.getMetrics_(); - var pos = (metrics.viewTop - metrics.scrollTop) + scrollDelta.y; + var metricsManager = this.workspace_.getMetricsManager(); + var scrollMetrics = metricsManager.getScrollMetrics(); + var viewMetrics = metricsManager.getViewMetrics(); + var pos = (viewMetrics.top - scrollMetrics.top) + scrollDelta.y; + this.workspace_.scrollbar.setY(pos); // When the flyout moves from a wheel event, hide WidgetDiv and DropDownDiv. Blockly.WidgetDiv.hide(); @@ -426,7 +367,7 @@ Blockly.VerticalFlyout.prototype.reflowInternal_ = function() { this.targetWorkspace.scrollX + flyoutWidth, this.targetWorkspace.scrollY); } - // Record the width for .getMetrics_ and .position. + // Record the width for workspace metrics and .position. this.width_ = flyoutWidth; this.position(); } diff --git a/core/metrics_manager.js b/core/metrics_manager.js index 17b63cf8d..62a8d7ee9 100644 --- a/core/metrics_manager.js +++ b/core/metrics_manager.js @@ -10,6 +10,7 @@ */ 'use strict'; +goog.provide('Blockly.FlyoutMetricsManager'); goog.provide('Blockly.MetricsManager'); goog.require('Blockly.IMetricsManager'); @@ -121,7 +122,8 @@ Blockly.MetricsManager.prototype.getDimensionsPx_ = function(elem) { * @public */ Blockly.MetricsManager.prototype.getFlyoutMetrics = function(opt_own) { - var flyoutDimensions = this.getDimensionsPx_(this.workspace_.getFlyout(opt_own)); + var flyoutDimensions = + this.getDimensionsPx_(this.workspace_.getFlyout(opt_own)); return { width: flyoutDimensions.width, height: flyoutDimensions.height, @@ -439,8 +441,7 @@ Blockly.MetricsManager.prototype.getMetrics = function() { var absoluteMetrics = this.getAbsoluteMetrics(); var viewMetrics = this.getViewMetrics(); var contentMetrics = this.getContentMetrics(); - var scrollMetrics = - this.getScrollMetrics(false, viewMetrics, contentMetrics); + var scrollMetrics = this.getScrollMetrics(false, viewMetrics, contentMetrics); return { contentHeight: contentMetrics.height, @@ -476,3 +477,116 @@ Blockly.MetricsManager.prototype.getMetrics = function() { Blockly.registry.register( Blockly.registry.Type.METRICS_MANAGER, Blockly.registry.DEFAULT, Blockly.MetricsManager); + +/** + * Calculates metrics for a flyout's workspace. + * The metrics are mainly used to size scrollbars for the flyout. + * @param {!Blockly.WorkspaceSvg} workspace The flyout's workspace. + * @param {!Blockly.IFlyout} flyout The flyout. + * @extends {Blockly.MetricsManager} + * @constructor + */ +Blockly.FlyoutMetricsManager = function(workspace, flyout) { + /** + * The flyout that owns the workspace to calculate metrics for. + * @type {!Blockly.IFlyout} + * @protected + */ + this.flyout_ = flyout; + + Blockly.FlyoutMetricsManager.superClass_.constructor.call(this, workspace); +}; +Blockly.utils.object.inherits( + Blockly.FlyoutMetricsManager, Blockly.MetricsManager); + +/** + * Gets the bounding box of the blocks on the flyout's workspace. + * This is in workspace coordinates. + * @returns {!SVGRect|{height: number, y: number, width: number, x: number}} The + * bounding box of the blocks on the workspace. + * @private + */ +Blockly.FlyoutMetricsManager.prototype.getBoundingBox_ = function() { + try { + var blockBoundingBox = this.workspace_.getCanvas().getBBox(); + } catch (e) { + // Firefox has trouble with hidden elements (Bug 528969). + // 2021 Update: It looks like this was fixed around Firefox 77 released in + // 2020. + var blockBoundingBox = {height: 0, y: 0, width: 0, x: 0}; + } + return blockBoundingBox; +}; + +/** + * @override + */ +Blockly.FlyoutMetricsManager.prototype.getContentMetrics = function( + opt_getWorkspaceCoordinates) { + // The bounding box is in workspace coordinates. + var blockBoundingBox = this.getBoundingBox_(); + var scale = opt_getWorkspaceCoordinates ? 1 : this.workspace_.scale; + + return { + height: blockBoundingBox.height * scale, + width: blockBoundingBox.width * scale, + top: blockBoundingBox.y * scale, + left: blockBoundingBox.x * scale, + }; +}; + +/** + * @override + */ +Blockly.FlyoutMetricsManager.prototype.getScrollMetrics = function( + opt_getWorkspaceCoordinates, opt_viewMetrics, opt_contentMetrics) { + var contentMetrics = opt_contentMetrics || this.getContentMetrics(); + var margin = this.flyout_.MARGIN; + var scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; + + return { + height: (contentMetrics.height + 2 * margin) / scale, + width: (contentMetrics.width + 2 * margin) / scale, + top: contentMetrics.top - margin / scale, + left: contentMetrics.left - margin / scale, + }; +}; + +/** + * @override + */ +Blockly.FlyoutMetricsManager.prototype.getViewMetrics = function( + opt_getWorkspaceCoordinates) { + var svgMetrics = this.getSvgMetrics(); + var scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; + if (this.flyout_.horizontalLayout) { + var viewWidth = svgMetrics.width - 2 * this.flyout_.SCROLLBAR_PADDING; + var viewHeight = svgMetrics.height - this.flyout_.SCROLLBAR_PADDING; + } else { + var viewWidth = svgMetrics.width - this.flyout_.SCROLLBAR_PADDING; + var viewHeight = svgMetrics.height - 2 * this.flyout_.SCROLLBAR_PADDING; + } + return { + height: viewHeight / scale, + width: viewWidth / scale, + top: -this.workspace_.scrollY / scale, + left: -this.workspace_.scrollX / scale, + }; +}; + +/** + * @override + */ +Blockly.FlyoutMetricsManager.prototype.getAbsoluteMetrics = function() { + var scrollbarPadding = this.flyout_.SCROLLBAR_PADDING; + + if (this.flyout_.horizontalLayout) { + // The viewWidth is svgWidth - 2 * scrollbarPadding. We want to put half + // of that padding to the left of the blocks. + return {top: 0, left: scrollbarPadding}; + } else { + // The viewHeight is svgHeight - 2 * scrollbarPadding. We want to put half + // of that padding to the top of the blocks. + return {top: scrollbarPadding, left: 0}; + } +}; diff --git a/core/mutator.js b/core/mutator.js index c4abea3e8..3530ae2e6 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -241,8 +241,8 @@ Blockly.Mutator.prototype.resizeBubble_ = function() { var height = workspaceSize.height + doubleBorderWidth * 3; var flyout = this.workspace_.getFlyout(); if (flyout) { - var flyoutMetrics = flyout.getMetrics_(); - height = Math.max(height, flyoutMetrics.scrollHeight + 20); + var flyoutScrollMetrics = this.workspace_.getMetricsManager().getScrollMetrics(); + height = Math.max(height, flyoutScrollMetrics.height + 20); width += flyout.getWidth(); } if (this.block_.RTL) { diff --git a/core/utils/metrics.js b/core/utils/metrics.js index cf982d241..dda790f4f 100644 --- a/core/utils/metrics.js +++ b/core/utils/metrics.js @@ -107,42 +107,42 @@ Blockly.utils.Metrics.prototype.absoluteLeft; /** * Height of the Blockly div (the view + the toolbox, simple of otherwise). - * @type {number|undefined} + * @type {number} */ Blockly.utils.Metrics.prototype.svgHeight; /** * Width of the Blockly div (the view + the toolbox, simple or otherwise). - * @type {number|undefined} + * @type {number} */ Blockly.utils.Metrics.prototype.svgWidth; /** * Width of the toolbox, if it exists. Otherwise zero. - * @type {number|undefined} + * @type {number} */ Blockly.utils.Metrics.prototype.toolboxWidth; /** * Height of the toolbox, if it exists. Otherwise zero. - * @type {number|undefined} + * @type {number} */ Blockly.utils.Metrics.prototype.toolboxHeight; /** * Top, bottom, left or right. Use TOOLBOX_AT constants to compare. - * @type {number|undefined} + * @type {number} */ Blockly.utils.Metrics.prototype.toolboxPosition; /** * Width of the flyout if it is always open. Otherwise zero. - * @type {number|undefined} + * @type {number} */ Blockly.utils.Metrics.prototype.flyoutWidth; /** * Height of the flyout if it is always open. Otherwise zero. - * @type {number|undefined} + * @type {number} */ Blockly.utils.Metrics.prototype.flyoutHeight; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 1b0a05655..3c668add1 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -532,6 +532,16 @@ Blockly.WorkspaceSvg.prototype.getMetricsManager = function() { }; /** + * Sets the metrics manager for the workspace. + * @param {!Blockly.IMetricsManager} metricsManager The metrics manager. + * @package + */ +Blockly.WorkspaceSvg.prototype.setMetricsManager = function(metricsManager) { + this.metricsManager_ = metricsManager; + this.getMetrics = this.metricsManager_.getMetrics.bind(this.metricsManager_); +}; + +/* * Gets the plugin manager for this workspace. * @return {!Blockly.PluginManager} The plugin manager. * @public diff --git a/tests/mocha/flyout_test.js b/tests/mocha/flyout_test.js index ba67d556f..4ee21b80b 100644 --- a/tests/mocha/flyout_test.js +++ b/tests/mocha/flyout_test.js @@ -34,13 +34,14 @@ suite('Flyout', function() { suite('simple flyout', function() { setup(function() { this.flyout = this.workspace.getFlyout(); + this.targetMetricsManager = this.flyout.targetWorkspace.getMetricsManager(); }); test('y is always 0', function() { chai.assert.equal(this.flyout.getY(), 0, 'y coordinate in vertical flyout should be 0'); }); test('x is right of workspace if flyout at right', function() { - sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ - viewWidth: 100, + sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({ + width: 100, }); this.flyout.targetWorkspace.toolboxPosition = Blockly.utils.toolbox.Position.RIGHT; @@ -62,25 +63,30 @@ suite('Flyout', function() { toolbox: toolbox }); this.flyout = this.workspace.getToolbox().getFlyout(); + this.targetMetricsManager = this.flyout.targetWorkspace.getMetricsManager(); }); teardown(function() { workspaceTeardown.call(this, this.workspace); }); test('x is aligned with toolbox at left', function() { - sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ - toolboxWidth: 20, + sinon.stub(this.targetMetricsManager, 'getToolboxMetrics').returns({ + width: 20, }); + this.flyout.setVisible(true); this.flyout.targetWorkspace.toolboxPosition = Blockly.utils.toolbox.Position.LEFT; this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.LEFT; chai.assert.equal(this.flyout.getX(), 20, 'x should be aligned with toolbox'); }); test('x is aligned with toolbox at right', function() { - sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ - toolboxWidth: 20, - viewWidth: 100, + sinon.stub(this.targetMetricsManager, 'getToolboxMetrics').returns({ + width: 20, + }); + sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({ + width: 100, }); this.flyout.width_ = 10; + this.flyout.setVisible(true); this.flyout.targetWorkspace.toolboxPosition = Blockly.utils.toolbox.Position.RIGHT; this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.RIGHT; @@ -92,6 +98,7 @@ suite('Flyout', function() { suite('trashcan flyout', function() { setup(function() { this.flyout = this.workspace.getFlyout(); + this.targetMetricsManager = this.flyout.targetWorkspace.getMetricsManager(); }); test('x is 0 if trashcan on left', function() { sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ @@ -104,10 +111,14 @@ suite('Flyout', function() { }); test('trashcan on right covers right edge of workspace', function() { this.flyout.width_ = 20; - sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ - viewWidth: 100, - absoluteLeft: 10, + sinon.stub(this.targetMetricsManager, 'getAbsoluteMetrics').returns({ + left: 10, }); + sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({ + width: 100, + }); + + this.flyout.setVisible(true); this.flyout.targetWorkspace.toolboxPosition = Blockly.utils.toolbox.Position.LEFT; this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.RIGHT; @@ -130,6 +141,7 @@ suite('Flyout', function() { suite('simple flyout', function() { setup(function() { this.flyout = this.workspace.getFlyout(); + this.targetMetricsManager = this.flyout.targetWorkspace.getMetricsManager(); }); test('x is always 0', function() { chai.assert.equal(this.flyout.getX(), 0, 'x coordinate in horizontal flyout should be 0'); @@ -144,8 +156,8 @@ suite('Flyout', function() { this.flyout.targetWorkspace.toolboxPosition = Blockly.utils.toolbox.Position.BOTTOM; this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.BOTTOM; - sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ - viewHeight: 50, + sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({ + height: 50, }); chai.assert.equal(this.flyout.getY(), 50, 'y should be below the workspace'); }); @@ -159,25 +171,31 @@ suite('Flyout', function() { horizontalLayout: true, }); this.flyout = this.workspace.getToolbox().getFlyout(); + this.targetMetricsManager = + this.flyout.targetWorkspace.getMetricsManager(); }); teardown(function() { workspaceTeardown.call(this, this.workspace); }); test('y is aligned with toolbox at top', function() { - sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ - toolboxHeight: 20, + sinon.stub(this.targetMetricsManager, 'getToolboxMetrics').returns({ + height: 20, }); + this.flyout.setVisible(true); this.flyout.targetWorkspace.toolboxPosition = Blockly.utils.toolbox.Position.TOP; this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.TOP; chai.assert.equal(this.flyout.getY(), 20, 'y should be aligned with toolbox'); }); test('y is aligned with toolbox at bottom', function() { - sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ - toolboxHeight: 20, - viewHeight: 100, + sinon.stub(this.targetMetricsManager, 'getToolboxMetrics').returns({ + height: 20, + }); + sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({ + height: 100, }); this.flyout.height_ = 30; + this.flyout.setVisible(true); this.flyout.targetWorkspace.toolboxPosition = Blockly.utils.toolbox.Position.BOTTOM; this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.BOTTOM; @@ -189,6 +207,8 @@ suite('Flyout', function() { suite('trashcan flyout', function() { setup(function() { this.flyout = this.workspace.getFlyout(); + this.targetMetricsManager = + this.flyout.targetWorkspace.getMetricsManager(); }); test('y is 0 if trashcan at top', function() { this.flyout.targetWorkspace.toolboxPosition = @@ -200,10 +220,13 @@ suite('Flyout', function() { this.flyout.targetWorkspace.toolboxPosition = Blockly.utils.toolbox.Position.TOP; this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.BOTTOM; - sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ - viewHeight: 50, - absoluteTop: 10, + sinon.stub(this.targetMetricsManager, 'getAbsoluteMetrics').returns({ + top: 10, }); + sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({ + height: 50, + }); + this.flyout.setVisible(true); this.flyout.height_ = 20; chai.assert.equal(this.flyout.getY(), 40, 'y + height should be aligned with bottom'); });