Updating bump logic to support single-direction scrollbars (#4652)

* Updating bump logic to support single-direction scrollbars
This commit is contained in:
Monica Kozbial
2021-03-01 12:20:12 -08:00
committed by GitHub
parent 5780399750
commit 57749e6eb8
16 changed files with 827 additions and 542 deletions

View File

@@ -200,11 +200,22 @@ Blockly.Events.COMMENT_MOVE = 'comment_move';
Blockly.Events.FINISHED_LOADING = 'finished_loading';
/**
* List of events that cause objects to be bumped back into the visible
* portion of the workspace (only used for non-movable workspaces).
* Type of events that cause objects to be bumped back into the visible
* portion of the workspace.
*
* Not to be confused with bumping so that disconnected connections to do
* not appear connected.
* Not to be confused with bumping so that disconnected connections do not
* appear connected.
* @typedef {!Blockly.Events.BlockCreate|!Blockly.Events.BlockMove|
* !Blockly.Events.CommentCreate|!Blockly.Events.CommentMove}
*/
Blockly.Events.BumpEvent;
/**
* List of events that cause objects to be bumped back into the visible
* portion of the workspace.
*
* Not to be confused with bumping so that disconnected connections do not
* appear connected.
* @const
*/
Blockly.Events.BUMP_EVENTS = [

View File

@@ -48,11 +48,15 @@ Blockly.utils.object.inherits(Blockly.HorizontalFlyout, Blockly.Flyout);
* .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.
@@ -83,11 +87,16 @@ Blockly.HorizontalFlyout.prototype.getMetrics_ = function() {
var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING;
var metrics = {
contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale,
contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale,
contentHeight: optionBox.height * this.workspace_.scale,
contentWidth: optionBox.width * this.workspace_.scale,
contentTop: 0,
contentLeft: 0,
scrollHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale,
scrollWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale,
scrollTop: 0,
scrollLeft: 0,
viewHeight: viewHeight,
viewWidth: viewWidth,
viewTop: -this.workspace_.scrollY,
@@ -115,8 +124,8 @@ Blockly.HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) {
if (typeof xyRatio.x == 'number') {
this.workspace_.scrollX =
-(metrics.contentLeft +
(metrics.contentWidth - metrics.viewWidth) * xyRatio.x);
-(metrics.scrollLeft +
(metrics.scrollWidth - metrics.viewWidth) * xyRatio.x);
}
this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft,
@@ -267,7 +276,7 @@ Blockly.HorizontalFlyout.prototype.wheel_ = function(e) {
if (delta) {
var metrics = this.getMetrics_();
var pos = metrics.viewLeft + delta;
var limit = metrics.contentWidth - metrics.viewWidth;
var limit = metrics.scrollWidth - metrics.viewWidth;
pos = Math.min(pos, limit);
pos = Math.max(pos, 0);
this.workspace_.scrollbar.setX(pos);

View File

@@ -55,9 +55,11 @@ Blockly.VerticalFlyout.registryName = 'verticalFlyout';
* .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.
@@ -87,10 +89,15 @@ Blockly.VerticalFlyout.prototype.getMetrics_ = function() {
}
var metrics = {
contentHeight: optionBox.height * this.workspace_.scale + 2 * this.MARGIN,
contentWidth: optionBox.width * this.workspace_.scale + 2 * this.MARGIN,
contentTop: optionBox.y - this.MARGIN,
contentLeft: optionBox.x - this.MARGIN,
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,
@@ -118,8 +125,8 @@ Blockly.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) {
}
if (typeof xyRatio.y == 'number') {
this.workspace_.scrollY =
-(metrics.contentTop +
(metrics.contentHeight - metrics.viewHeight) * xyRatio.y);
-(metrics.scrollTop +
(metrics.scrollHeight - metrics.viewHeight) * xyRatio.y);
}
this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft,
this.workspace_.scrollY + metrics.absoluteTop);
@@ -258,8 +265,8 @@ Blockly.VerticalFlyout.prototype.wheel_ = function(e) {
if (scrollDelta.y) {
var metrics = this.getMetrics_();
var pos = (metrics.viewTop - metrics.contentTop) + scrollDelta.y;
var limit = metrics.contentHeight - metrics.viewHeight;
var pos = (metrics.viewTop - metrics.scrollTop) + scrollDelta.y;
var limit = metrics.scrollHeight - metrics.viewHeight;
pos = Math.min(pos, limit);
pos = Math.max(pos, 0);
this.workspace_.scrollbar.setY(pos);

View File

@@ -181,147 +181,7 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
// A null translation will also apply the correct initial scale.
mainWorkspace.translate(0, 0);
if (!wsOptions.readOnly && !mainWorkspace.isMovable()) {
// Helper function for the workspaceChanged callback.
// TODO (#2300): Move metrics math back to the WorkspaceSvg.
var getWorkspaceMetrics = function() {
var workspaceMetrics = Object.create(null);
var defaultMetrics = mainWorkspace.getMetrics();
var scale = mainWorkspace.scale;
workspaceMetrics.RTL = mainWorkspace.RTL;
// Get the view metrics in workspace units.
workspaceMetrics.viewLeft = defaultMetrics.viewLeft / scale;
workspaceMetrics.viewTop = defaultMetrics.viewTop / scale;
workspaceMetrics.viewRight =
(defaultMetrics.viewLeft + defaultMetrics.viewWidth) / scale;
workspaceMetrics.viewBottom =
(defaultMetrics.viewTop + defaultMetrics.viewHeight) / scale;
// Get the exact content metrics (in workspace units), even if the
// content is bounded.
if (mainWorkspace.isContentBounded()) {
// Already in workspace units, no need to divide by scale.
var blocksBoundingBox = mainWorkspace.getBlocksBoundingBox();
workspaceMetrics.contentLeft = blocksBoundingBox.left;
workspaceMetrics.contentTop = blocksBoundingBox.top;
workspaceMetrics.contentRight = blocksBoundingBox.right;
workspaceMetrics.contentBottom = blocksBoundingBox.bottom;
} else {
workspaceMetrics.contentLeft = defaultMetrics.contentLeft / scale;
workspaceMetrics.contentTop = defaultMetrics.contentTop / scale;
workspaceMetrics.contentRight =
(defaultMetrics.contentLeft + defaultMetrics.contentWidth) / scale;
workspaceMetrics.contentBottom =
(defaultMetrics.contentTop + defaultMetrics.contentHeight) / scale;
}
return workspaceMetrics;
};
var getObjectMetrics = function(object) {
var objectMetrics = object.getBoundingRectangle();
objectMetrics.height = objectMetrics.bottom - objectMetrics.top;
objectMetrics.width = objectMetrics.right - objectMetrics.left;
return objectMetrics;
};
var bumpObjects = function(e) {
// We always check isMovable_ again because the original
// "not movable" state of isMovable_ could have been changed.
if (!mainWorkspace.isDragging() && !mainWorkspace.isMovable() &&
(Blockly.Events.BUMP_EVENTS.indexOf(e.type) != -1)) {
var metrics = getWorkspaceMetrics();
if (metrics.contentTop < metrics.viewTop ||
metrics.contentBottom > metrics.viewBottom ||
metrics.contentLeft < metrics.viewLeft ||
metrics.contentRight > metrics.viewRight) {
// Handle undo.
var oldGroup = null;
if (e) {
oldGroup = Blockly.Events.getGroup();
Blockly.Events.setGroup(e.group);
}
switch (e.type) {
case Blockly.Events.BLOCK_CREATE:
case Blockly.Events.BLOCK_MOVE:
var object = mainWorkspace.getBlockById(e.blockId);
if (object) {
object = object.getRootBlock();
}
break;
case Blockly.Events.COMMENT_CREATE:
case Blockly.Events.COMMENT_MOVE:
var object = mainWorkspace.getCommentById(e.commentId);
break;
}
if (object) {
var objectMetrics = getObjectMetrics(object);
// The idea is to find the region of valid coordinates for the top
// left corner of the object, and then clamp the object's
// top left corner within that region.
// The top of the object should always be at or below the top of
// the workspace.
var topClamp = metrics.viewTop;
// The top of the object should ideally be positioned so that
// the bottom of the object is not below the bottom of the
// workspace.
var bottomClamp = metrics.viewBottom - objectMetrics.height;
// If the object is taller than the workspace we want to
// top-align the block, which means setting the bottom clamp to
// match.
bottomClamp = Math.max(topClamp, bottomClamp);
var newYPosition = Blockly.utils.math.clamp(
topClamp, objectMetrics.top, bottomClamp);
var deltaY = newYPosition - objectMetrics.top;
// Note: Even in RTL mode the "anchor" of the object is the
// top-left corner of the object.
// The left edge of the object should ideally be positioned at
// or to the right of the left edge of the workspace.
var leftClamp = metrics.viewLeft;
// The left edge of the object should ideally be positioned so
// that the right of the object is not outside the workspace bounds.
var rightClamp = metrics.viewRight - objectMetrics.width;
if (metrics.RTL) {
// If the object is wider than the workspace and we're in RTL
// mode we want to right-align the block, which means setting
// the left clamp to match.
leftClamp = Math.min(rightClamp, leftClamp);
} else {
// If the object is wider than the workspace and we're in LTR
// mode we want to left-align the block, which means setting
// the right clamp to match.
rightClamp = Math.max(leftClamp, rightClamp);
}
var newXPosition = Blockly.utils.math.clamp(
leftClamp, objectMetrics.left, rightClamp);
var deltaX = newXPosition - objectMetrics.left;
object.moveBy(deltaX, deltaY);
}
if (e) {
if (!e.group && object) {
console.warn('Moved object in bounds but there was no' +
' event group. This may break undo.');
}
if (oldGroup !== null) {
Blockly.Events.setGroup(oldGroup);
}
}
}
}
};
mainWorkspace.addChangeListener(bumpObjects);
}
mainWorkspace.addChangeListener(Blockly.bumpIntoBoundsHandler_(mainWorkspace));
// The SVG is now fully assembled.
Blockly.svgResize(mainWorkspace);
@@ -331,6 +191,142 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
return mainWorkspace;
};
/**
* Extracts the object from the given event.
* @param {!Blockly.WorkspaceSvg} workspace The workspace the event originated
* from.
* @param {!Blockly.Events.BumpEvent} e An event containing an object.
* @return {?Blockly.BlockSvg|?Blockly.WorkspaceCommentSvg} The extracted
* object.
* @private
*/
Blockly.extractObjectFromEvent_ = function(workspace, e) {
var object = null;
switch (e.type) {
case Blockly.Events.BLOCK_CREATE:
case Blockly.Events.BLOCK_MOVE:
object = workspace.getBlockById(e.blockId);
if (object) {
object = object.getRootBlock();
}
break;
case Blockly.Events.COMMENT_CREATE:
case Blockly.Events.COMMENT_MOVE:
object = workspace.getCommentById(e.commentId);
break;
}
return object;
};
/**
* Bumps the top objects in the given workspace into bounds.
* @param {!Blockly.WorkspaceSvg} workspace The workspace.
* @private
*/
Blockly.bumpTopObjectsIntoBounds_ = function(workspace) {
var metricsManager = workspace.getMetricsManager();
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
return;
}
var scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
var topBlocks = workspace.getTopBoundedElements();
for (var i = 0, block; (block = topBlocks[i]); i++) {
Blockly.bumpObjectIntoBounds_(
workspace, scrollMetricsInWsCoords, block);
}
};
/**
* Creates a handler for bumping objects when they cross fixed bounds.
* @param {!Blockly.WorkspaceSvg} workspace The workspace to handle.
* @return {function(Blockly.Events.Abstract)} The event handler.
* @private
*/
Blockly.bumpIntoBoundsHandler_ = function(workspace) {
return function(e) {
var metricsManager = workspace.getMetricsManager();
if (!metricsManager.hasFixedEdges() || workspace.isDragging() ||
Blockly.Events.BUMP_EVENTS.indexOf(e.type) === -1) {
return;
}
var scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
// Triggered by move/create event
var object = Blockly.extractObjectFromEvent_(workspace, e);
if (!object) {
return;
}
// Handle undo.
var oldGroup = Blockly.Events.getGroup();
Blockly.Events.setGroup(e.group);
var wasBumped = Blockly.bumpObjectIntoBounds_(
workspace, scrollMetricsInWsCoords,
/** @type {!Blockly.IBoundedElement} */ (object));
if (wasBumped && !e.group) {
console.warn('Moved object in bounds but there was no' +
' event group. This may break undo.');
}
if (oldGroup !== null) {
Blockly.Events.setGroup(oldGroup);
}
};
};
/**
* Bumps the given object that has passed out of bounds.
* @param {!Blockly.WorkspaceSvg} workspace The workspace containing the object.
* @param {!Blockly.MetricsManager.ContainerRegion} scrollMetrics Scroll metrics
* in workspace coordinates.
* @param {!Blockly.IBoundedElement} object The object to bump.
* @return {boolean} True if block was bumped.
* @private
*/
Blockly.bumpObjectIntoBounds_ = function(workspace, scrollMetrics, object) {
// Compute new top/left position for object.
var objectMetrics = object.getBoundingRectangle();
var height = objectMetrics.bottom - objectMetrics.top;
var width = objectMetrics.right - objectMetrics.left;
var topClamp = scrollMetrics.top;
var scrollMetricsBottom = scrollMetrics.top + scrollMetrics.height;
var bottomClamp = scrollMetricsBottom - height;
// If the object is taller than the workspace we want to
// top-align the block
var newYPosition =
Blockly.utils.math.clamp(topClamp, objectMetrics.top, bottomClamp);
var deltaY = newYPosition - objectMetrics.top;
// Note: Even in RTL mode the "anchor" of the object is the
// top-left corner of the object.
var leftClamp = scrollMetrics.left;
var scrollMetricsRight = scrollMetrics.left + scrollMetrics.width;
var rightClamp = scrollMetricsRight - width;
if (workspace.RTL) {
// If the object is wider than the workspace and we're in RTL
// mode we want to right-align the block, which means setting
// the left clamp to match.
leftClamp = Math.min(rightClamp, leftClamp);
} else {
// If the object is wider than the workspace and we're in LTR
// mode we want to left-align the block, which means setting
// the right clamp to match.
rightClamp = Math.max(leftClamp, rightClamp);
}
var newXPosition =
Blockly.utils.math.clamp(leftClamp, objectMetrics.left, rightClamp);
var deltaX = newXPosition - objectMetrics.left;
if (deltaX || deltaY) {
object.moveBy(deltaX, deltaY);
return true;
}
return false;
};
/**
* Initialize Blockly with various handlers.
* @param {!Blockly.WorkspaceSvg} mainWorkspace Newly created main workspace.
@@ -353,6 +349,7 @@ Blockly.init_ = function(mainWorkspace) {
Blockly.browserEvents.conditionalBind(window, 'resize', null, function() {
Blockly.hideChaff(true);
Blockly.svgResize(mainWorkspace);
Blockly.bumpTopObjectsIntoBounds_(mainWorkspace);
});
mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler);

View File

@@ -29,3 +29,10 @@ Blockly.IBoundedElement = function() {};
* @return {!Blockly.utils.Rect} Object with coordinates of the bounded element.
*/
Blockly.IBoundedElement.prototype.getBoundingRectangle;
/**
* Move the element by a relative offset.
* @param {number} dx Horizontal offset in workspace units.
* @param {number} dy Vertical offset in workspace units.
*/
Blockly.IBoundedElement.prototype.moveBy;

View File

@@ -28,30 +28,26 @@ goog.requireType('Blockly.utils.toolbox');
Blockly.IMetricsManager = function() {};
/**
* Gets the width and the height of the flyout on the workspace in pixel
* coordinates.
* @param {!Blockly.MetricsManager.ContainerRegion} viewMetrics 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 {!Blockly.MetricsManager.ContainerRegion} 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.
* @protected
* Returns whether the scroll area has fixed edges.
* @return {boolean} Whether the scroll area has fixed edges.
* @package
*/
Blockly.IMetricsManager.prototype.getContentDimensionsBounded_;
Blockly.IMetricsManager.prototype.hasFixedEdges;
/**
* Gets the bounding box for all workspace contents, in pixel coordinates.
* @return {!Blockly.MetricsManager.ContainerRegion} The dimensions of the
* contents of the given workspace in pixel coordinates, as an object
* containing
* - height and width in pixels
* - left and top in pixels relative to the workspace origin.
* @protected
* Returns the metrics for the scroll area of the workspace.
* @param {boolean=} opt_getWorkspaceCoordinates True to get the scroll metrics
* in workspace coordinates, false to get them in pixel coordinates.
* @param {!Blockly.MetricsManager.ContainerRegion=} opt_viewMetrics The view
* metrics if they have been previously computed. Passing in null may cause
* the view metrics to be computed again, if it is needed.
* @param {!Blockly.MetricsManager.ContainerRegion=} opt_contentMetrics The
* content metrics if they have been previously computed. Passing in null
* may cause the content metrics to be computed again, if it is needed.
* @return {!Blockly.MetricsManager.ContainerRegion} The metrics for the scroll
* container
*/
Blockly.IMetricsManager.prototype.getContentDimensionsExact_;
Blockly.IMetricsManager.prototype.getScrollMetrics;
/**
* Gets the width and the height of the flyout on the workspace in pixel
@@ -107,19 +103,10 @@ Blockly.IMetricsManager.prototype.getViewMetrics;
/**
* Gets content metrics in either pixel or workspace coordinates.
*
* This can mean two things:
* If the workspace has a fixed width and height then the content
* area is rectangle around all the top bounded elements on the workspace
* (workspace comments and blocks).
*
* If the workspace does not have a fixed width and height then it is the
* metrics of the area that content can be placed.
* The content area is a rectangle around all the top bounded elements on the
* workspace (workspace comments and blocks).
* @param {boolean=} opt_getWorkspaceCoordinates True to get the content metrics
* in workspace coordinates, false to get them in pixel coordinates.
* @param {!Blockly.MetricsManager.ContainerRegion=} opt_viewMetrics The view
* metrics if they have been previously computed. Passing in null may cause
* the view metrics to be computed again, if it is needed.
* @return {!Blockly.MetricsManager.ContainerRegion} The
* metrics for the content container.
* @public

View File

@@ -71,6 +71,17 @@ Blockly.MetricsManager.AbsoluteMetrics;
*/
Blockly.MetricsManager.ContainerRegion;
/**
* Describes fixed edges of the workspace.
* @typedef {{
* top: (number|undefined),
* bottom: (number|undefined),
* left: (number|undefined),
* right: (number|undefined)
* }}
*/
Blockly.MetricsManager.FixedEdges;
/**
* Gets the dimensions of the given workspace component, in pixel coordinates.
* @param {?Blockly.IToolbox|?Blockly.IFlyout} elem The element to get the
@@ -90,66 +101,6 @@ Blockly.MetricsManager.prototype.getDimensionsPx_ = function(elem) {
return new Blockly.utils.Size(width, height);
};
/**
* Calculates the size of a scrollable workspace, which should include
* room for a half screen border around the workspace contents. In pixel
* coordinates.
* @param {!Blockly.MetricsManager.ContainerRegion} viewMetrics 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 {!Blockly.MetricsManager.ContainerRegion} 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.
* @protected
*/
Blockly.MetricsManager.prototype.getContentDimensionsBounded_ = function(
viewMetrics) {
var content = this.getContentDimensionsExact_();
var contentRight = content.left + content.width;
var contentBottom = content.top + content.height;
// View height and width are both in pixels, and are the same as the SVG size.
var viewWidth = viewMetrics.width;
var viewHeight = viewMetrics.height;
var halfWidth = viewWidth / 2;
var halfHeight = viewHeight / 2;
// Add a border around the content that is at least half a screen wide.
// Ensure border is wide enough that blocks can scroll over entire screen.
var left = Math.min(content.left - halfWidth, contentRight - viewWidth);
var right = Math.max(contentRight + halfWidth, content.left + viewWidth);
var top = Math.min(content.top - halfHeight, contentBottom - viewHeight);
var bottom = Math.max(contentBottom + halfHeight, content.top + viewHeight);
return {left: left, top: top, height: bottom - top, width: right - left};
};
/**
* Gets the bounding box for all workspace contents, in pixel coordinates.
* @return {!Blockly.MetricsManager.ContainerRegion} The dimensions of the
* contents of the given workspace in pixel coordinates, as an object
* containing
* - height and width in pixels
* - left and top in pixels relative to the workspace origin.
* @protected
*/
Blockly.MetricsManager.prototype.getContentDimensionsExact_ = function() {
// Block bounding box is in workspace coordinates.
var blockBox = this.workspace_.getBlocksBoundingBox();
var scale = this.workspace_.scale;
// Convert to pixels.
var top = blockBox.top * scale;
var bottom = blockBox.bottom * scale;
var left = blockBox.left * scale;
var right = blockBox.right * scale;
return {top: top, left: left, width: right - left, height: bottom - top};
};
/**
* Gets the width and the height of the flyout on the workspace in pixel
* coordinates. Returns 0 for the width and height if the workspace has a
@@ -280,40 +231,145 @@ Blockly.MetricsManager.prototype.getViewMetrics = function(
/**
* Gets content metrics in either pixel or workspace coordinates.
*
* This can mean two things:
* If the workspace has a fixed width and height then the content
* area is rectangle around all the top bounded elements on the workspace
* (workspace comments and blocks).
*
* If the workspace does not have a fixed width and height then it is the
* metrics of the area that content can be placed. This area is computed by
* getting the rectangle around the top bounded elements on the workspace and
* adding padding to all sides.
* The content area is a rectangle around all the top bounded elements on the
* workspace (workspace comments and blocks).
* @param {boolean=} opt_getWorkspaceCoordinates True to get the content metrics
* in workspace coordinates, false to get them in pixel coordinates.
* @param {!Blockly.MetricsManager.ContainerRegion=} opt_viewMetrics The view
* metrics if they have been previously computed. Not passing in view
* metrics may cause them to be computed again.
* @return {!Blockly.MetricsManager.ContainerRegion} The
* metrics for the content container.
* @public
*/
Blockly.MetricsManager.prototype.getContentMetrics = function(
opt_getWorkspaceCoordinates, opt_viewMetrics) {
var scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1;
var contentDimensions = null;
if (this.workspace_.isContentBounded()) {
opt_viewMetrics = opt_viewMetrics || this.getViewMetrics(false);
contentDimensions = this.getContentDimensionsBounded_(opt_viewMetrics);
} else {
contentDimensions = this.getContentDimensionsExact_();
}
opt_getWorkspaceCoordinates) {
var scale = opt_getWorkspaceCoordinates ? 1 : this.workspace_.scale;
// Block bounding box is in workspace coordinates.
var blockBox = this.workspace_.getBlocksBoundingBox();
return {
height: contentDimensions.height / scale,
width: contentDimensions.width / scale,
top: contentDimensions.top / scale,
left: contentDimensions.left / scale,
height: (blockBox.bottom - blockBox.top) * scale,
width: (blockBox.right - blockBox.left) * scale,
top: blockBox.top * scale,
left: blockBox.left * scale,
};
};
/**
* Returns whether the scroll area has fixed edges.
* @return {boolean} Whether the scroll area has fixed edges.
* @package
*/
Blockly.MetricsManager.prototype.hasFixedEdges = function() {
// This exists for optimization of bump logic.
return !this.workspace_.isMovableHorizontally() ||
!this.workspace_.isMovableVertically();
};
/**
* Computes the fixed edges of the scroll area.
* @param {!Blockly.MetricsManager.ContainerRegion=} opt_viewMetrics The view
* metrics if they have been previously computed. Passing in null may cause
* the view metrics to be computed again, if it is needed.
* @return {Blockly.MetricsManager.FixedEdges} The fixed edges of the scroll
* area.
* @protected
*/
Blockly.MetricsManager.prototype.getComputedFixedEdges_ = function(
opt_viewMetrics) {
if (!this.hasFixedEdges()) {
// Return early if there are no edges.
return {};
}
var hScrollEnabled = this.workspace_.isMovableHorizontally();
var vScrollEnabled = this.workspace_.isMovableVertically();
var viewMetrics = opt_viewMetrics || this.getViewMetrics(false);
var edges = {};
if (!vScrollEnabled) {
edges.top = viewMetrics.top;
edges.bottom = viewMetrics.top + viewMetrics.height;
}
if (!hScrollEnabled) {
edges.left = viewMetrics.left;
edges.right = viewMetrics.left + viewMetrics.width;
}
return edges;
};
/**
* Returns the content area with added padding.
* @param {!Blockly.MetricsManager.ContainerRegion} viewMetrics The view
* metrics.
* @param {!Blockly.MetricsManager.ContainerRegion} contentMetrics The content
* metrics.
* @return {{top: number, bottom: number, left: number, right: number}} The
* padded content area.
* @protected
*/
Blockly.MetricsManager.prototype.getPaddedContent_ = function(
viewMetrics, contentMetrics) {
var contentBottom = contentMetrics.top + contentMetrics.height;
var contentRight = contentMetrics.left + contentMetrics.width;
var viewWidth = viewMetrics.width;
var viewHeight = viewMetrics.height;
var halfWidth = viewWidth / 2;
var halfHeight = viewHeight / 2;
// Add a padding around the content that is at least half a screen wide.
// Ensure padding is wide enough that blocks can scroll over entire screen.
var top =
Math.min(contentMetrics.top - halfHeight, contentBottom - viewHeight);
var left =
Math.min(contentMetrics.left - halfWidth, contentRight - viewWidth);
var bottom =
Math.max(contentBottom + halfHeight, contentMetrics.top + viewHeight);
var right =
Math.max(contentRight + halfWidth, contentMetrics.left + viewWidth);
return {top: top, bottom: bottom, left: left, right: right};
};
/**
* Returns the metrics for the scroll area of the workspace.
* @param {boolean=} opt_getWorkspaceCoordinates True to get the scroll metrics
* in workspace coordinates, false to get them in pixel coordinates.
* @param {!Blockly.MetricsManager.ContainerRegion=} opt_viewMetrics The view
* metrics if they have been previously computed. Passing in null may cause
* the view metrics to be computed again, if it is needed.
* @param {!Blockly.MetricsManager.ContainerRegion=} opt_contentMetrics The
* content metrics if they have been previously computed. Passing in null
* may cause the content metrics to be computed again, if it is needed.
* @return {!Blockly.MetricsManager.ContainerRegion} The metrics for the scroll
* container
*/
Blockly.MetricsManager.prototype.getScrollMetrics = function(
opt_getWorkspaceCoordinates, opt_viewMetrics, opt_contentMetrics) {
var scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1;
var viewMetrics = opt_viewMetrics || this.getViewMetrics(false);
var contentMetrics = opt_contentMetrics || this.getContentMetrics();
var fixedEdges = this.getComputedFixedEdges_(viewMetrics);
// Add padding around content
var paddedContent = this.getPaddedContent_(viewMetrics, contentMetrics);
// Use combination of fixed bounds and padded content to make scroll area.
var top = fixedEdges.top !== undefined ?
fixedEdges.top : paddedContent.top;
var left = fixedEdges.left !== undefined ?
fixedEdges.left : paddedContent.left;
var bottom = fixedEdges.bottom !== undefined ?
fixedEdges.bottom : paddedContent.bottom;
var right = fixedEdges.right !== undefined ?
fixedEdges.right : paddedContent.right;
return {
top: top / scale,
left: left / scale,
width: (right - left) / scale,
height: (bottom - top) / scale,
};
};
@@ -325,6 +381,8 @@ Blockly.MetricsManager.prototype.getContentMetrics = function(
* .viewWidth: Width of the visible portion of the workspace.
* .contentHeight: Height of the content.
* .contentWidth: Width of the content.
* .scrollHeight: Height of the scroll area.
* .scrollWidth: Width of the scroll area.
* .svgHeight: Height of the Blockly div (the view + the toolbox,
* simple or otherwise),
* .svgWidth: Width of the Blockly div (the view + the toolbox,
@@ -335,6 +393,8 @@ Blockly.MetricsManager.prototype.getContentMetrics = function(
* the workspace origin.
* .contentTop: Top-edge of the content, relative to the workspace origin.
* .contentLeft: Left-edge of the content relative to the workspace origin.
* .scrollTop: Top-edge of the scroll area, relative to the workspace origin.
* .scrollLeft: Left-edge of the scroll area relative to the workspace origin.
* .absoluteTop: Top-edge of the visible portion of the workspace, relative
* to the blocklyDiv.
* .absoluteLeft: Left-edge of the visible portion of the workspace, relative
@@ -355,7 +415,9 @@ Blockly.MetricsManager.prototype.getMetrics = function() {
var svgMetrics = this.getSvgMetrics();
var absoluteMetrics = this.getAbsoluteMetrics();
var viewMetrics = this.getViewMetrics();
var contentMetrics = this.getContentMetrics(false, viewMetrics);
var contentMetrics = this.getContentMetrics();
var scrollMetrics =
this.getScrollMetrics(false, viewMetrics, contentMetrics);
return {
contentHeight: contentMetrics.height,
@@ -363,6 +425,11 @@ Blockly.MetricsManager.prototype.getMetrics = function() {
contentTop: contentMetrics.top,
contentLeft: contentMetrics.left,
scrollHeight: scrollMetrics.height,
scrollWidth: scrollMetrics.width,
scrollTop: scrollMetrics.top,
scrollLeft: scrollMetrics.left,
viewHeight: viewMetrics.height,
viewWidth: viewMetrics.width,
viewTop: viewMetrics.top,

View File

@@ -469,6 +469,11 @@ Blockly.Mutator.prototype.getFlyoutMetrics_ = function() {
contentTop: unsupported,
contentLeft: unsupported,
scrollHeight: unsupported,
scrollWidth: unsupported,
scrollTop: unsupported,
scrollLeft: unsupported,
viewHeight: this.workspaceHeight_,
viewWidth: this.workspaceWidth_ - flyoutWidth,
viewTop: unsupported,

View File

@@ -128,15 +128,15 @@ Blockly.ScrollbarPair.prototype.resize = function() {
} else {
// Has the content been resized or moved?
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.contentWidth != hostMetrics.contentWidth ||
this.oldHostMetrics_.scrollWidth != hostMetrics.scrollWidth ||
this.oldHostMetrics_.viewLeft != hostMetrics.viewLeft ||
this.oldHostMetrics_.contentLeft != hostMetrics.contentLeft) {
this.oldHostMetrics_.scrollLeft != hostMetrics.scrollLeft) {
resizeH = true;
}
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.contentHeight != hostMetrics.contentHeight ||
this.oldHostMetrics_.scrollHeight != hostMetrics.scrollHeight ||
this.oldHostMetrics_.viewTop != hostMetrics.viewTop ||
this.oldHostMetrics_.contentTop != hostMetrics.contentTop) {
this.oldHostMetrics_.scrollTop != hostMetrics.scrollTop) {
resizeV = true;
}
}
@@ -174,7 +174,6 @@ Blockly.ScrollbarPair.prototype.resize = function() {
this.oldHostMetrics_ = hostMetrics;
};
/**
* Returns whether scrolling horizontally is enabled.
* @return {boolean} True if horizontal scroll is enabled.
@@ -428,12 +427,13 @@ Blockly.Scrollbar.prototype.origin_ = new Blockly.utils.Coordinate(0, 0);
Blockly.Scrollbar.prototype.startDragMouse_ = 0;
/**
* The size of the area within which the scrollbar handle can move, in CSS
* pixels (the size of the scrollbar background).
* The length of the scrollbars (including the handle and the background), in
* CSS pixels. This is equivalent to scrollbar background length and the area
* within which the scrollbar handle can move.
* @type {number}
* @private
*/
Blockly.Scrollbar.prototype.scrollViewSize_ = 0;
Blockly.Scrollbar.prototype.scrollbarLength_ = 0;
/**
* The length of the scrollbar handle in CSS pixels.
@@ -485,7 +485,7 @@ Blockly.Scrollbar.SCROLLBAR_MARGIN = 0.5;
/**
* @param {Blockly.utils.Metrics} first An object containing computed
* measurements of a workspace.
* @param {Blockly.utils.Metrics} second Another object containing computed
* @param {?Blockly.utils.Metrics} second Another object containing computed
* measurements of a workspace.
* @return {boolean} Whether the two sets of metrics are equivalent.
* @private
@@ -501,10 +501,10 @@ Blockly.Scrollbar.metricsAreEquivalent_ = function(first, second) {
first.viewTop != second.viewTop ||
first.absoluteTop != second.absoluteTop ||
first.absoluteLeft != second.absoluteLeft ||
first.contentWidth != second.contentWidth ||
first.contentHeight != second.contentHeight ||
first.contentLeft != second.contentLeft ||
first.contentTop != second.contentTop) {
first.scrollWidth != second.scrollWidth ||
first.scrollHeight != second.scrollHeight ||
first.scrollLeft != second.scrollLeft ||
first.scrollTop != second.scrollTop) {
return false;
}
@@ -541,11 +541,11 @@ Blockly.Scrollbar.prototype.dispose = function() {
* @return {number} Constrained value, in CSS pixels.
* @private
*/
Blockly.Scrollbar.prototype.constrainLength_ = function(value) {
Blockly.Scrollbar.prototype.constrainHandleLength_ = function(value) {
if (value <= 0 || isNaN(value)) {
value = 0;
} else {
value = Math.min(value, this.scrollViewSize_);
value = Math.min(value, this.scrollbarLength_);
}
return value;
};
@@ -568,14 +568,14 @@ Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) {
* @return {number} Constrained value, in CSS pixels.
* @private
*/
Blockly.Scrollbar.prototype.constrainPosition_ = function(value) {
Blockly.Scrollbar.prototype.constrainHandlePosition_ = function(value) {
if (value <= 0 || isNaN(value)) {
value = 0;
} else {
// Handle length should never be greater than this.scrollViewSize_.
// If the viewSize is greater than or equal to the contentSize, the
// handleLength will end up equal to this.scrollViewSize_.
value = Math.min(value, this.scrollViewSize_ - this.handleLength_);
// Handle length should never be greater than this.scrollbarLength_.
// If the viewSize is greater than or equal to the scrollSize, the
// handleLength will end up equal to this.scrollbarLength_.
value = Math.min(value, this.scrollbarLength_ - this.handleLength_);
}
return value;
};
@@ -596,10 +596,10 @@ Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) {
* @param {number} newSize The new scrollbar background length in CSS pixels.
* @private
*/
Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) {
this.scrollViewSize_ = newSize;
this.outerSvg_.setAttribute(this.lengthAttribute_, this.scrollViewSize_);
this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_);
Blockly.Scrollbar.prototype.setScrollbarLength_ = function(newSize) {
this.scrollbarLength_ = newSize;
this.outerSvg_.setAttribute(this.lengthAttribute_, this.scrollbarLength_);
this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollbarLength_);
};
/**
@@ -697,7 +697,7 @@ Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
// Shorten the scrollbar to make room for the corner square.
viewSize -= Blockly.Scrollbar.scrollbarThickness;
}
this.setScrollViewSize_(Math.max(0, viewSize));
this.setScrollbarLength_(Math.max(0, viewSize));
var xCoordinate =
hostMetrics.absoluteLeft + Blockly.Scrollbar.SCROLLBAR_MARGIN;
@@ -722,10 +722,10 @@ Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
* the required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) {
if (hostMetrics.viewWidth >= hostMetrics.contentWidth) {
// viewWidth is often greater than contentWidth in flyouts and
if (hostMetrics.viewWidth >= hostMetrics.scrollWidth) {
// viewWidth is often greater than scrollWidth in flyouts and
// non-scrollable workspaces.
this.setHandleLength_(this.scrollViewSize_);
this.setHandleLength_(this.scrollbarLength_);
this.setHandlePosition(0);
if (!this.pair_) {
// The scrollbar isn't needed.
@@ -741,27 +741,27 @@ Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) {
// Resize the handle.
var handleLength =
this.scrollViewSize_ * hostMetrics.viewWidth / hostMetrics.contentWidth;
handleLength = this.constrainLength_(handleLength);
this.scrollbarLength_ * hostMetrics.viewWidth / hostMetrics.scrollWidth;
handleLength = this.constrainHandleLength_(handleLength);
this.setHandleLength_(handleLength);
// Compute the handle offset.
// The position of the handle can be between:
// 0 and this.scrollViewSize_ - handleLength
// If viewLeft == contentLeft
// 0 and this.scrollbarLength_ - handleLength
// If viewLeft == scrollLeft
// then the offset should be 0
// If viewRight == contentRight
// then viewLeft = contentLeft + contentWidth - viewWidth
// If viewRight == scrollRight
// then viewLeft = scrollLeft + scrollWidth - viewWidth
// then the offset should be max offset
var maxScrollDistance = hostMetrics.contentWidth - hostMetrics.viewWidth;
var contentDisplacement = hostMetrics.viewLeft - hostMetrics.contentLeft;
var maxScrollDistance = hostMetrics.scrollWidth - hostMetrics.viewWidth;
var contentDisplacement = hostMetrics.viewLeft - hostMetrics.scrollLeft;
// Percent of content to the left of our current position.
var offsetRatio = contentDisplacement / maxScrollDistance;
// Area available to scroll * percent to the left
var maxHandleOffset = this.scrollViewSize_ - this.handleLength_;
var maxHandleOffset = this.scrollbarLength_ - this.handleLength_;
var handleOffset = maxHandleOffset * offsetRatio;
handleOffset = this.constrainPosition_(handleOffset);
handleOffset = this.constrainHandlePosition_(handleOffset);
this.setHandlePosition(handleOffset);
// Compute ratio (for use with set calls, which pass in content displacement).
@@ -794,7 +794,7 @@ Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
// Shorten the scrollbar to make room for the corner square.
viewSize -= Blockly.Scrollbar.scrollbarThickness;
}
this.setScrollViewSize_(Math.max(0, viewSize));
this.setScrollbarLength_(Math.max(0, viewSize));
var xCoordinate = this.workspace_.RTL ?
hostMetrics.absoluteLeft + Blockly.Scrollbar.SCROLLBAR_MARGIN :
@@ -817,10 +817,10 @@ Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
* the required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
if (hostMetrics.viewHeight >= hostMetrics.contentHeight) {
// viewHeight is often greater than contentHeight in flyouts and
if (hostMetrics.viewHeight >= hostMetrics.scrollHeight) {
// viewHeight is often greater than scrollHeight in flyouts and
// non-scrollable workspaces.
this.setHandleLength_(this.scrollViewSize_);
this.setHandleLength_(this.scrollbarLength_);
this.setHandlePosition(0);
if (!this.pair_) {
// The scrollbar isn't needed.
@@ -836,27 +836,27 @@ Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
// Resize the handle.
var handleLength =
this.scrollViewSize_ * hostMetrics.viewHeight / hostMetrics.contentHeight;
handleLength = this.constrainLength_(handleLength);
this.scrollbarLength_ * hostMetrics.viewHeight / hostMetrics.scrollHeight;
handleLength = this.constrainHandleLength_(handleLength);
this.setHandleLength_(handleLength);
// Compute the handle offset.
// The position of the handle can be between:
// 0 and this.scrollViewSize_ - handleLength
// If viewTop == contentTop
// 0 and this.scrollbarLength_ - handleLength
// If viewTop == scrollTop
// then the offset should be 0
// If viewBottom == contentBottom
// then viewTop = contentTop + contentHeight - viewHeight
// If viewBottom == scrollBottom
// then viewTop = scrollTop + scrollHeight - viewHeight
// then the offset should be max offset
var maxScrollDistance = hostMetrics.contentHeight - hostMetrics.viewHeight;
var contentDisplacement = hostMetrics.viewTop - hostMetrics.contentTop;
var maxScrollDistance = hostMetrics.scrollHeight - hostMetrics.viewHeight;
var contentDisplacement = hostMetrics.viewTop - hostMetrics.scrollTop;
// Percent of content to the left of our current position.
var offsetRatio = contentDisplacement / maxScrollDistance;
// Area available to scroll * percent to the left
var maxHandleOffset = this.scrollViewSize_ - this.handleLength_;
var maxHandleOffset = this.scrollbarLength_ - this.handleLength_;
var handleOffset = maxHandleOffset * offsetRatio;
handleOffset = this.constrainPosition_(handleOffset);
handleOffset = this.constrainHandlePosition_(handleOffset);
this.setHandlePosition(handleOffset);
// Compute ratio (for use with set calls, which pass in content displacement).
@@ -1005,7 +1005,7 @@ Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) {
handlePosition += pageLength;
}
this.setHandlePosition(this.constrainPosition_(handlePosition));
this.setHandlePosition(this.constrainHandlePosition_(handlePosition));
this.updateMetrics_();
e.stopPropagation();
@@ -1055,7 +1055,7 @@ Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) {
var mouseDelta = currentMouse - this.startDragMouse_;
var handlePosition = this.startDragHandle + mouseDelta;
// Position the bar.
this.setHandlePosition(this.constrainPosition_(handlePosition));
this.setHandlePosition(this.constrainHandlePosition_(handlePosition));
this.updateMetrics_();
};
@@ -1093,7 +1093,7 @@ Blockly.Scrollbar.prototype.cleanUp_ = function() {
* @protected
*/
Blockly.Scrollbar.prototype.getRatio_ = function() {
var scrollHandleRange = this.scrollViewSize_ - this.handleLength_;
var scrollHandleRange = this.scrollbarLength_ - this.handleLength_;
var ratio = this.handlePosition_ / scrollHandleRange;
if (isNaN(ratio)) {
ratio = 0;
@@ -1125,7 +1125,7 @@ Blockly.Scrollbar.prototype.updateMetrics_ = function() {
* Defaults to true.
*/
Blockly.Scrollbar.prototype.set = function(value, updateMetrics) {
this.setHandlePosition(this.constrainPosition_(value * this.ratio));
this.setHandlePosition(this.constrainHandlePosition_(value * this.ratio));
if (updateMetrics || updateMetrics === undefined) {
this.updateMetrics_();
}

View File

@@ -343,7 +343,10 @@ Blockly.Toolbox.prototype.createFlyout_ = function() {
'oneBasedIndex': workspace.options.oneBasedIndex,
'horizontalLayout': workspace.horizontalLayout,
'renderer': workspace.options.renderer,
'rendererOverrides': workspace.options.rendererOverrides
'rendererOverrides': workspace.options.rendererOverrides,
'move': {
'scrollbars': true,
}
}));
// Options takes in either 'end' or 'start'. This has already been parsed to
// be either 0 or 1, so set it after.

View File

@@ -351,7 +351,7 @@ Blockly.Tooltip.show_ = function() {
div.appendChild(document.createTextNode(lines[i]));
Blockly.Tooltip.DIV.appendChild(div);
}
var rtl = Blockly.Tooltip.element_.RTL;
var rtl = /** @type {{RTL: boolean}} */ (Blockly.Tooltip.element_).RTL;
var windowWidth = document.documentElement.clientWidth;
var windowHeight = document.documentElement.clientHeight;
// Display the tooltip.

View File

@@ -69,7 +69,10 @@ Blockly.Trashcan = function(workspace) {
'rtl': this.workspace_.RTL,
'oneBasedIndex': this.workspace_.options.oneBasedIndex,
'renderer': this.workspace_.options.renderer,
'rendererOverrides': this.workspace_.options.rendererOverrides
'rendererOverrides': this.workspace_.options.rendererOverrides,
'move': {
'scrollbars': true,
}
}));
// Create vertical or horizontal flyout.
if (this.workspace_.horizontalLayout) {

View File

@@ -42,6 +42,18 @@ Blockly.utils.Metrics.prototype.contentHeight;
*/
Blockly.utils.Metrics.prototype.contentWidth;
/**
* Height of the scroll area.
* @type {number}
*/
Blockly.utils.Metrics.prototype.scrollHeight;
/**
* Width of the scroll area.
* @type {number}
*/
Blockly.utils.Metrics.prototype.scrollWidth;
/**
* Top-edge of the visible portion of the workspace, relative to the workspace
* origin.
@@ -68,6 +80,18 @@ Blockly.utils.Metrics.prototype.contentTop;
*/
Blockly.utils.Metrics.prototype.contentLeft;
/**
* Top-edge of the scroll area, relative to the workspace origin.
* @type {number}
*/
Blockly.utils.Metrics.prototype.scrollTop;
/**
* Left-edge of the scroll area relative to the workspace origin.
* @type {number}
*/
Blockly.utils.Metrics.prototype.scrollLeft;
/**
* Top-edge of the visible portion of the workspace, relative to the blocklyDiv.
* @type {number}

View File

@@ -38,15 +38,14 @@ Blockly.WorkspaceDragger = function(workspace) {
* @type {boolean}
* @private
*/
this.horizontalScrollEnabled_ =
this.workspace_.scrollbar.canScrollHorizontally();
this.horizontalScrollEnabled_ = this.workspace_.isMovableHorizontally();
/**
* Whether vertical scroll is enabled.
* @type {boolean}
* @private
*/
this.verticalScrollEnabled_ = this.workspace_.scrollbar.canScrollVertically();
this.verticalScrollEnabled_ = this.workspace_.isMovableVertically();
/**
* The scroll position of the workspace at the beginning of the drag.

View File

@@ -986,7 +986,10 @@ Blockly.WorkspaceSvg.prototype.addFlyout = function(tagName) {
'oneBasedIndex': this.options.oneBasedIndex,
'horizontalLayout': this.horizontalLayout,
'renderer': this.options.renderer,
'rendererOverrides': this.options.rendererOverrides
'rendererOverrides': this.options.rendererOverrides,
'move': {
'scrollbars': true,
}
}));
workspaceOptions.toolboxPosition = this.options.toolboxPosition;
if (this.horizontalLayout) {
@@ -1157,11 +1160,11 @@ Blockly.WorkspaceSvg.prototype.maybeFireViewportChangeEvent = function() {
// negligible changes in viewport top/left.
return;
}
var event = new (Blockly.Events.get(Blockly.Events.VIEWPORT_CHANGE))(top,
left, scale, this.id);
this.oldScale_ = scale;
this.oldTop_ = top;
this.oldLeft_ = left;
var event = new (Blockly.Events.get(Blockly.Events.VIEWPORT_CHANGE))(top,
left, scale, this.id);
Blockly.Events.fire(event);
};
@@ -1629,22 +1632,6 @@ Blockly.WorkspaceSvg.prototype.isDraggable = function() {
return this.options.moveOptions && this.options.moveOptions.drag;
};
/**
* Should the workspace have bounded content? Used to tell if the
* workspace's content should be sized so that it can move (bounded) or not
* (exact sizing).
* @return {boolean} True if the workspace should be bounded, false otherwise.
* @package
*/
Blockly.WorkspaceSvg.prototype.isContentBounded = function() {
return (this.options.moveOptions && this.options.moveOptions.scrollbars) ||
(this.options.moveOptions && this.options.moveOptions.wheel) ||
(this.options.moveOptions && this.options.moveOptions.drag) ||
(this.options.zoomOptions && this.options.zoomOptions.controls) ||
(this.options.zoomOptions && this.options.zoomOptions.wheel) ||
(this.options.zoomOptions && this.options.zoomOptions.pinch);
};
/**
* Is this workspace movable?
*
@@ -1663,6 +1650,28 @@ Blockly.WorkspaceSvg.prototype.isMovable = function() {
(this.options.zoomOptions && this.options.zoomOptions.pinch);
};
/**
* Is this workspace movable horizontally?
* @return {boolean} True if the workspace is movable horizontally, false
* otherwise.
*/
Blockly.WorkspaceSvg.prototype.isMovableHorizontally = function() {
var hasScrollbars = !!this.scrollbar;
return this.isMovable() && (!hasScrollbars ||
(hasScrollbars && this.scrollbar.canScrollHorizontally()));
};
/**
* Is this workspace movable vertically?
* @return {boolean} True if the workspace is movable vertically, false
* otherwise.
*/
Blockly.WorkspaceSvg.prototype.isMovableVertically = function() {
var hasScrollbars = !!this.scrollbar;
return this.isMovable() && (!hasScrollbars ||
(hasScrollbars && this.scrollbar.canScrollVertically()));
};
/**
* Handle a mouse-wheel on SVG drawing surface.
* @param {!Event} e Mouse wheel event.
@@ -2032,12 +2041,12 @@ Blockly.WorkspaceSvg.prototype.scrollCenter = function() {
}
var metrics = this.getMetrics();
var x = (metrics.contentWidth - metrics.viewWidth) / 2;
var y = (metrics.contentHeight - metrics.viewHeight) / 2;
var x = (metrics.scrollWidth - metrics.viewWidth) / 2;
var y = (metrics.scrollHeight - metrics.viewHeight) / 2;
// Convert from workspace directions to canvas directions.
x = -x - metrics.contentLeft;
y = -y - metrics.contentTop;
x = -x - metrics.scrollLeft;
y = -y - metrics.scrollTop;
this.scroll(x, y);
};
@@ -2124,10 +2133,11 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) {
// zoom correctly without scrollbars, but scroll does not resize the
// scrollbars so we have to call resizeView/resizeContent as well.
var metrics = this.getMetrics();
// The scroll values and the view values are additive inverses of
// each other, so when we subtract from one we have to add to the other.
this.scrollX -= metrics.absoluteLeft;
this.scrollY -= metrics.absoluteTop;
// // The scroll values and the view values are additive inverses of
// // each other, so when we subtract from one we have to add to the other.
metrics.viewLeft += metrics.absoluteLeft;
metrics.viewTop += metrics.absoluteTop;
@@ -2167,22 +2177,18 @@ Blockly.WorkspaceSvg.prototype.scroll = function(x, y) {
// Keep scrolling within the bounds of the content.
var metrics = this.getMetrics();
// This is the offset of the top-left corner of the view from the
// workspace origin when the view is "seeing" the bottom-right corner of
// the content.
var maxOffsetOfViewFromOriginX = metrics.contentWidth + metrics.contentLeft -
metrics.viewWidth;
var maxOffsetOfViewFromOriginY = metrics.contentHeight + metrics.contentTop -
metrics.viewHeight;
// Canvas coordinates (aka scroll coordinates) have inverse directionality
// to workspace coordinates so we have to inverse them.
x = Math.min(x, -metrics.contentLeft);
y = Math.min(y, -metrics.contentTop);
x = Math.max(x, -maxOffsetOfViewFromOriginX);
y = Math.max(y, -maxOffsetOfViewFromOriginY);
x = Math.min(x, -metrics.scrollLeft);
y = Math.min(y, -metrics.scrollTop);
var maxXScroll = metrics.scrollLeft + metrics.scrollWidth - metrics.viewWidth;
var maxYScroll =
metrics.scrollTop + metrics.scrollHeight - metrics.viewHeight;
x = Math.max(x, -maxXScroll);
y = Math.max(y, -maxYScroll);
this.scrollX = x;
this.scrollY = y;
if (this.scrollbar) {
// The content position (displacement from the content's top-left to the
// origin) plus the scroll position (displacement from the view's top-left
@@ -2191,7 +2197,7 @@ Blockly.WorkspaceSvg.prototype.scroll = function(x, y) {
// the content's top-left to the view's top-left, matching the
// directionality of the scrollbars.
this.scrollbar.set(
-(x + metrics.contentLeft), -(y + metrics.contentTop), false);
-(x + metrics.scrollLeft), -(y + metrics.scrollTop), false);
}
// We have to shift the translation so that when the canvas is at 0, 0 the
// workspace origin is not underneath the toolbox.
@@ -2212,13 +2218,13 @@ Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) {
if (typeof xyRatio.x == 'number') {
this.scrollX =
-(metrics.contentLeft +
(metrics.contentWidth - metrics.viewWidth) * xyRatio.x);
-(metrics.scrollLeft +
(metrics.scrollWidth - metrics.viewWidth) * xyRatio.x);
}
if (typeof xyRatio.y == 'number') {
this.scrollY =
-(metrics.contentTop +
(metrics.contentHeight - metrics.viewHeight) * xyRatio.y);
-(metrics.scrollTop +
(metrics.scrollHeight - metrics.viewHeight) * xyRatio.y);
}
// We have to shift the translation so that when the canvas is at 0, 0 the
// workspace origin is not underneath the toolbox.

View File

@@ -32,7 +32,8 @@ suite('Metrics', function() {
scale: scale,
scrollX: SCROLL_X,
scrollY: SCROLL_Y,
isContentBounded: function() {}
isMovableHorizontally: function() { return true; },
isMovableVertically: function() { return true; }
};
}
@@ -43,125 +44,6 @@ suite('Metrics', function() {
sharedTestTeardown.call(this);
});
suite('getContentDimensionsExact_', function() {
test('Empty', function() {
var ws = makeMockWs(1, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var defaultZoom = metricsManager.getContentDimensionsExact_(ws);
assertDimensionsMatch(defaultZoom, 0, 0, 0, 0);
});
test('Empty zoom in', function() {
var ws = makeMockWs(2, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var zoomIn = metricsManager.getContentDimensionsExact_(ws);
assertDimensionsMatch(zoomIn, 0, 0, 0, 0);
});
test('Empty zoom out', function() {
var ws = makeMockWs(.5, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var zoomOut = metricsManager.getContentDimensionsExact_(ws);
assertDimensionsMatch(zoomOut, 0, 0, 0, 0);
});
test('Non empty at origin', function() {
var ws = makeMockWs(1, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var defaultZoom = metricsManager.getContentDimensionsExact_(ws);
// Pixel and ws units are the same at default zoom.
assertDimensionsMatch(defaultZoom, 0, 0, 100, 100);
});
test('Non empty at origin zoom in', function() {
var ws = makeMockWs(2, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var zoomIn = metricsManager.getContentDimensionsExact_(ws);
// 1 ws unit = 2 pixels at this zoom level.
assertDimensionsMatch(zoomIn, 0, 0, 200, 200);
});
test('Non empty at origin zoom out', function() {
var ws = makeMockWs(.5, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var zoomOut = metricsManager.getContentDimensionsExact_(ws);
// 1 ws unit = 0.5 pixels at this zoom level.
assertDimensionsMatch(zoomOut, 0, 0, 50, 50);
});
test('Non empty positive origin', function() {
var ws = makeMockWs(1, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var defaultZoom = metricsManager.getContentDimensionsExact_(ws);
// Pixel and ws units are the same at default zoom.
assertDimensionsMatch(defaultZoom, 10, 10, 100, 100);
});
test('Non empty positive origin zoom in', function() {
var ws = makeMockWs(2, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var zoomIn = metricsManager.getContentDimensionsExact_(ws);
// 1 ws unit = 2 pixels at this zoom level.
assertDimensionsMatch(zoomIn, 20, 20, 200, 200);
});
test('Non empty positive origin zoom out', function() {
var ws = makeMockWs(.5, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var zoomOut = metricsManager.getContentDimensionsExact_(ws);
// 1 ws unit = 0.5 pixels at this zoom level.
assertDimensionsMatch(zoomOut, 5, 5, 50, 50);
});
test('Non empty negative origin', function() {
var ws = makeMockWs(1, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var defaultZoom = metricsManager.getContentDimensionsExact_(ws);
// Pixel and ws units are the same at default zoom.
assertDimensionsMatch(defaultZoom, -10, -10, 100, 100);
});
test('Non empty negative origin zoom in', function() {
var ws = makeMockWs(2, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var zoomIn = metricsManager.getContentDimensionsExact_(ws);
// 1 ws unit = 2 pixels at this zoom level.
assertDimensionsMatch(zoomIn, -20, -20, 200, 200);
});
test('Non empty negative origin zoom out', function() {
var ws = makeMockWs(.5, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var zoomOut = metricsManager.getContentDimensionsExact_(ws);
// 1 ws unit = 0.5 pixels at this zoom level.
assertDimensionsMatch(zoomOut, -5, -5, 50, 50);
});
});
suite('getContentDimensionsBounded_', function() {
setup(function() {
this.ws = makeMockWs(1, 0, 0, 0, 0);
this.metricsManager = new Blockly.MetricsManager(this.ws);
this.contentDimensionsStub =
sinon.stub(this.metricsManager, 'getContentDimensionsExact_');
});
test('Empty workspace', function() {
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentDimensions = {top: 0, left: 0, width: 0, height: 0};
this.contentDimensionsStub.returns(mockContentDimensions);
var contentMetrics =
this.metricsManager.getContentDimensionsBounded_(mockViewMetrics);
// Should add half the view width to all sides.
assertDimensionsMatch(contentMetrics, -200, -200, 400, 400);
});
test('Non empty workspace', function() {
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentDimensions = {top: 100, left: 100, width: 50, height: 50};
this.contentDimensionsStub.returns(mockContentDimensions);
var contentMetrics =
this.metricsManager.getContentDimensionsBounded_(mockViewMetrics);
// Should add half of the view width to all sides.
assertDimensionsMatch(contentMetrics, -50, -50, 350, 350);
});
});
suite('getAbsoluteMetrics', function() {
setup(function() {
this.ws = makeMockWs(1, 0, 0, 0, 0);
@@ -296,54 +178,332 @@ suite('Metrics', function() {
});
suite('getContentMetrics', function() {
setup(function() {
this.ws = makeMockWs(1, 0, 0, 0, 0);
this.metricsManager = new Blockly.MetricsManager(this.ws);
this.viewMetricsStub = sinon.stub(this.metricsManager, 'getViewMetrics');
this.isContentBoundedStub =
sinon.stub(this.metricsManager.workspace_, 'isContentBounded');
this.getBoundedMetricsStub =
sinon.stub(this.metricsManager, 'getContentDimensionsBounded_');
this.getExactMetricsStub =
sinon.stub(this.metricsManager, 'getContentDimensionsExact_');
test('Empty in ws coordinates', function() {
var ws = makeMockWs(1, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, 0, 0, 0, 0);
});
test('Content Dimensions in pixel coordinates bounded ws', function() {
this.isContentBoundedStub.returns(true);
this.getBoundedMetricsStub.returns(
{height: 100, width: 100, left: 100, top: 100});
var contentMetrics = this.metricsManager.getContentMetrics(false);
// Should return what getContentDimensionsBounded_ returns.
assertDimensionsMatch(contentMetrics, 100, 100, 100, 100);
sinon.assert.calledOnce(this.getBoundedMetricsStub);
sinon.assert.calledOnce(this.viewMetricsStub);
test('Empty zoom-in in ws coordinates', function() {
var ws = makeMockWs(2, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, 0, 0, 0, 0);
});
test('Content Dimensions in pixel coordinates exact ws', function() {
this.isContentBoundedStub.returns(false);
this.getExactMetricsStub.returns(
{height: 100, width: 100, left: 100, top: 100});
var contentMetrics = this.metricsManager.getContentMetrics(false);
// Should return what getContentDimensionsExact_ returns.
assertDimensionsMatch(contentMetrics, 100, 100, 100, 100);
sinon.assert.calledOnce(this.getExactMetricsStub);
sinon.assert.notCalled(this.viewMetricsStub);
test('Empty zoom-out in ws coordinates', function() {
var ws = makeMockWs(.5, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, 0, 0, 0, 0);
});
test('Content Dimensions in ws coordinates bounded ws', function() {
var getWorkspaceCoordinates = true;
this.ws.scale = 2;
this.isContentBoundedStub.returns(true);
this.getBoundedMetricsStub.returns(
{height: 100, width: 100, left: 100, top: 100});
test('Non empty at origin ws coordinates', function() {
var ws = makeMockWs(1, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, 0, 0, 100, 100);
});
test('Non empty at origin zoom-in ws coordinates', function() {
var ws = makeMockWs(2, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, 0, 0, 100, 100);
});
test('Non empty at origin zoom-out ws coordinates', function() {
var ws = makeMockWs(.5, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, 0, 0, 100, 100);
});
test('Non empty positive origin ws coordinates', function() {
var ws = makeMockWs(1, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, 10, 10, 100, 100);
});
test('Non empty positive origin zoom-in ws coordinates', function() {
var ws = makeMockWs(2, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
// 1 ws unit = 2 pixels at this zoom level.
assertDimensionsMatch(contentMetrics, 10, 10, 100, 100);
});
test('Non empty positive origin zoom-out ws coordinates', function() {
var ws = makeMockWs(.5, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
// 1 ws unit = 0.5 pixels at this zoom level.
assertDimensionsMatch(contentMetrics, 10, 10, 100, 100);
});
test('Non empty negative origin ws coordinates', function() {
var ws = makeMockWs(1, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
// Pixel and ws units are the same at default zoom.
assertDimensionsMatch(contentMetrics, -10, -10, 100, 100);
});
test('Non empty negative origin zoom-in ws coordinates', function() {
var ws = makeMockWs(2, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, -10, -10, 100, 100);
});
test('Non empty negative origin zoom-out ws coordinates', function() {
var ws = makeMockWs(.5, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(true);
assertDimensionsMatch(contentMetrics, -10, -10, 100, 100);
});
test('Empty in pixel coordinates', function() {
var ws = makeMockWs(1, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
assertDimensionsMatch(contentMetrics, 0, 0, 0, 0);
});
test('Empty zoom-in in pixel coordinates', function() {
var ws = makeMockWs(2, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
assertDimensionsMatch(contentMetrics, 0, 0, 0, 0);
});
test('Empty zoom-out in pixel coordinates', function() {
var ws = makeMockWs(.5, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
assertDimensionsMatch(contentMetrics, 0, 0, 0, 0);
});
test('Non empty at origin pixel coordinates', function() {
var ws = makeMockWs(1, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// Pixel and ws units are the same at default zoom.
assertDimensionsMatch(contentMetrics, 0, 0, 100, 100);
});
test('Non empty at origin zoom-in pixel coordinates', function() {
var ws = makeMockWs(2, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// 1 ws unit = 2 pixels at this zoom level.
assertDimensionsMatch(contentMetrics, 0, 0, 200, 200);
});
test('Non empty at origin zoom-out pixel coordinates', function() {
var ws = makeMockWs(.5, 0, 0, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// 1 ws unit = 0.5 pixels at this zoom level.
assertDimensionsMatch(contentMetrics, 0, 0, 50, 50);
});
test('Non empty positive origin pixel coordinates', function() {
var ws = makeMockWs(1, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// Pixel and ws units are the same at default zoom.
assertDimensionsMatch(contentMetrics, 10, 10, 100, 100);
});
test('Non empty positive origin zoom-in pixel coordinates', function() {
var ws = makeMockWs(2, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// 1 ws unit = 2 pixels at this zoom level.
assertDimensionsMatch(contentMetrics, 20, 20, 200, 200);
});
test('Non empty positive origin zoom-out pixel coordinates', function() {
var ws = makeMockWs(.5, 10, 10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// 1 ws unit = 0.5 pixels at this zoom level.
assertDimensionsMatch(contentMetrics, 5, 5, 50, 50);
});
test('Non empty negative origin pixel coordinates', function() {
var ws = makeMockWs(1, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// Pixel and ws units are the same at default zoom.
assertDimensionsMatch(contentMetrics, -10, -10, 100, 100);
});
test('Non empty negative origin zoom-in pixel coordinates', function() {
var ws = makeMockWs(2, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// 1 ws unit = 2 pixels at this zoom level.
assertDimensionsMatch(contentMetrics, -20, -20, 200, 200);
});
test('Non empty negative origin zoom-out pixel coordinates', function() {
var ws = makeMockWs(.5, -10, -10, 100, 100);
var metricsManager = new Blockly.MetricsManager(ws);
var contentMetrics = metricsManager.getContentMetrics(false);
// 1 ws unit = 0.5 pixels at this zoom level.
assertDimensionsMatch(contentMetrics, -5, -5, 50, 50);
});
});
suite('getScrollMetrics', function() {
test('Empty workspace in ws coordinates', function() {
var ws = makeMockWs(1, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 0, left: 0, width: 0, height: 0};
var contentMetrics =
this.metricsManager.getContentMetrics(getWorkspaceCoordinates);
metricsManager.getScrollMetrics(true, mockViewMetrics, mockContentMetrics);
assertDimensionsMatch(contentMetrics, 50, 50, 50, 50);
sinon.assert.calledOnce(this.getBoundedMetricsStub);
sinon.assert.calledOnce(this.viewMetricsStub);
// Should add half the view width to all sides.
assertDimensionsMatch(contentMetrics, -200, -200, 400, 400);
});
test('Empty workspace zoom-in in ws coordinates', function() {
var ws = makeMockWs(2, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 0, left: 0, width: 0, height: 0};
var contentMetrics =
metricsManager.getScrollMetrics(true, mockViewMetrics, mockContentMetrics);
// Should add half the view width to all sides.
assertDimensionsMatch(contentMetrics, -100, -100, 200, 200);
});
test('Empty workspace zoom-out in ws coordinates', function() {
var ws = makeMockWs(0.5, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 0, left: 0, width: 0, height: 0};
var contentMetrics =
metricsManager.getScrollMetrics(true, mockViewMetrics, mockContentMetrics);
// Should add half the view width to all sides.
assertDimensionsMatch(contentMetrics, -400, -400, 800, 800);
});
test('Non empty workspace in ws coordinates', function() {
var ws = makeMockWs(1, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 100, left: 100, width: 50, height: 50};
var contentMetrics =
metricsManager.getScrollMetrics(true, mockViewMetrics, mockContentMetrics);
// Should add half of the view width to all sides.
assertDimensionsMatch(contentMetrics, -50, -50, 350, 350);
});
test('Non empty workspace zoom-in in ws coordinates', function() {
var ws = makeMockWs(2, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 100, left: 100, width: 50, height: 50};
var contentMetrics =
metricsManager.getScrollMetrics(true, mockViewMetrics, mockContentMetrics);
// Should add half of the view width to all sides.
assertDimensionsMatch(contentMetrics, -25, -25, 175, 175);
});
test('Non empty workspace zoom-out in ws coordinates', function() {
var ws = makeMockWs(0.5, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 100, left: 100, width: 50, height: 50};
var contentMetrics =
metricsManager.getScrollMetrics(true, mockViewMetrics, mockContentMetrics);
// Should add half of the view width to all sides.
assertDimensionsMatch(contentMetrics, -100, -100, 700, 700);
});
test('Empty workspace in pixel coordinates', function() {
var ws = makeMockWs(1, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 0, left: 0, width: 0, height: 0};
var contentMetrics =
metricsManager.getScrollMetrics(false, mockViewMetrics, mockContentMetrics);
// Should add half the view width to all sides.
assertDimensionsMatch(contentMetrics, -200, -200, 400, 400);
});
test('Empty workspace zoom-in in pixel coordinates', function() {
var ws = makeMockWs(2, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 0, left: 0, width: 0, height: 0};
var contentMetrics =
metricsManager.getScrollMetrics(false, mockViewMetrics, mockContentMetrics);
// Should add half the view width to all sides.
assertDimensionsMatch(contentMetrics, -200, -200, 400, 400);
});
test('Empty workspace zoom-out in pixel coordinates', function() {
var ws = makeMockWs(0.5, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 0, left: 0, width: 0, height: 0};
var contentMetrics =
metricsManager.getScrollMetrics(false, mockViewMetrics, mockContentMetrics);
// Should add half the view width to all sides.
assertDimensionsMatch(contentMetrics, -200, -200, 400, 400);
});
test('Non empty workspace in pixel coordinates', function() {
var ws = makeMockWs(1, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 100, left: 100, width: 50, height: 50};
var contentMetrics =
metricsManager.getScrollMetrics(false, mockViewMetrics, mockContentMetrics);
// Should add half of the view width to all sides.
assertDimensionsMatch(contentMetrics, -50, -50, 350, 350);
});
test('Non empty workspace zoom-in in pixel coordinates', function() {
var ws = makeMockWs(2, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 100, left: 100, width: 50, height: 50};
var contentMetrics =
metricsManager.getScrollMetrics(false, mockViewMetrics, mockContentMetrics);
// Should add half of the view width to all sides.
assertDimensionsMatch(contentMetrics, -50, -50, 350, 350);
});
test('Non empty workspace zoom-out in pixel coordinates', function() {
var ws = makeMockWs(0.5, 0, 0, 0, 0);
var metricsManager = new Blockly.MetricsManager(ws);
// The location of the viewport.
var mockViewMetrics = {top: 0, left: 0, width: 200, height: 200};
// The bounding box around the blocks on the screen.
var mockContentMetrics = {top: 100, left: 100, width: 50, height: 50};
var contentMetrics =
metricsManager.getScrollMetrics(false, mockViewMetrics, mockContentMetrics);
// Should add half of the view width to all sides.
assertDimensionsMatch(contentMetrics, -50, -50, 350, 350);
});
});
});