diff --git a/core/inject.js b/core/inject.js index 7a392c96c..af345aa64 100644 --- a/core/inject.js +++ b/core/inject.js @@ -247,6 +247,8 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, 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; @@ -276,6 +278,13 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, 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. @@ -306,31 +315,54 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, break; } if (object) { - var objectMetrics = object.getBoundingRectangle(); + var objectMetrics = getObjectMetrics(object); - // Bump any object that's above the top back inside. - var overflowTop = metrics.viewTop - objectMetrics.top; - if (overflowTop > 0) { - object.moveBy(0, overflowTop); + // 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); } - // Bump any object that's below the bottom back inside. - var overflowBottom = metrics.viewBottom - objectMetrics.bottom; - if (overflowBottom < 0) { - object.moveBy(0, overflowBottom); - } + var newXPosition = Blockly.utils.math.clamp( + leftClamp, objectMetrics.left, rightClamp); + var deltaX = newXPosition - objectMetrics.left; - // Bump any object that's off the left back inside. - var overflowLeft = metrics.viewLeft - objectMetrics.left; - if (overflowLeft > 0) { - object.moveBy(overflowLeft, 0); - } - - // Bump any object that's off the right back inside. - var overflowRight = metrics.viewRight - objectMetrics.right; - if (overflowRight < 0) { - object.moveBy(overflowRight, 0); - } + object.moveBy(deltaX, deltaY); } if (e) { if (!e.group) {