diff --git a/core/flyout_horizontal.js b/core/flyout_horizontal.js index 68184a883..6a0817823 100644 --- a/core/flyout_horizontal.js +++ b/core/flyout_horizontal.js @@ -118,6 +118,62 @@ Blockly.HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) { this.workspace_.scrollY + metrics.absoluteTop); }; +/** + * Calculates the x coordinate for the flyout position. + * @return {number} X coordinate. + */ +Blockly.HorizontalFlyout.prototype.getX = function() { + // X is always 0 since this is a horizontal flyout. + return 0; +}; + +/** + * Calculates the y coordinate for the flyout position. + * @return {number} Y coordinate. + */ +Blockly.HorizontalFlyout.prototype.getY = function() { + var targetWorkspaceMetrics = this.targetWorkspace.getMetrics(); + if (!targetWorkspaceMetrics) { + // Hidden components will return null. + return 0; + } + + var y = 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.toolboxHeight) { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + y = targetWorkspaceMetrics.toolboxHeight; + } else { + y = targetWorkspaceMetrics.viewHeight - this.height_; + } + // Simple (flyout-only) toolbox. + } else { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + y = 0; + } else { + // The simple flyout does not cover the workspace. + y = targetWorkspaceMetrics.viewHeight; + } + } + // Trashcan flyout is opposite the main flyout. + } else { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + y = 0; + } else { + // Because the anchor point of the flyout is on the top, but we want + // 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_; + } + } + + return y; +}; + /** * Move the flyout to the edge of the workspace. */ @@ -137,36 +193,9 @@ Blockly.HorizontalFlyout.prototype.position = function() { var edgeHeight = this.height_ - this.CORNER_RADIUS; this.setBackgroundPath_(edgeWidth, edgeHeight); - // X is always 0 since this is a horizontal flyout. - var x = 0; - // If this flyout is the toolbox flyout. - if (this.targetWorkspace.toolboxPosition == this.toolboxPosition_) { - // If there is a toolbox. - if (targetWorkspaceMetrics.toolboxHeight) { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - var y = targetWorkspaceMetrics.toolboxHeight; - } else { - var y = targetWorkspaceMetrics.viewHeight - this.height_; - } - } else { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - var y = 0; - } else { - var y = targetWorkspaceMetrics.viewHeight; - } - } - } else { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - var y = 0; - } else { - // Because the anchor point of the flyout is on the top, but we want - // 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. - var y = targetWorkspaceMetrics.viewHeight + - targetWorkspaceMetrics.absoluteTop - this.height_; - } - } + var x = this.getX(); + var y = this.getY(); + this.positionAt_(this.width_, this.height_, x, y); }; diff --git a/core/flyout_vertical.js b/core/flyout_vertical.js index 05687154c..9e62c966f 100644 --- a/core/flyout_vertical.js +++ b/core/flyout_vertical.js @@ -121,6 +121,63 @@ Blockly.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) { this.workspace_.scrollY + metrics.absoluteTop); }; +/** + * Calculates the x coordinate for the flyout position. + * @return {number} X coordinate. + */ +Blockly.VerticalFlyout.prototype.getX = function() { + var targetWorkspaceMetrics = this.targetWorkspace.getMetrics(); + if (!targetWorkspaceMetrics) { + // Hidden components will return null. + return 0; + } + + 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 (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + x = targetWorkspaceMetrics.toolboxWidth; + } else { + x = targetWorkspaceMetrics.viewWidth - this.width_; + } + // Simple (flyout-only) toolbox. + } else { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + x = 0; + } else { + // The simple flyout does not cover the workspace. + x = targetWorkspaceMetrics.viewWidth; + } + } + // Trashcan flyout is opposite the main flyout. + } else { + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + x = 0; + } else { + // Because the anchor point of the flyout is on the left, but we want + // 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_; + } + } + + return x; +}; + +/** + * Calculates the y coordinate for the flyout position. + * @return {number} Y coordinate. + */ +Blockly.VerticalFlyout.prototype.getY = function() { + // Y is always 0 since this is a vertical flyout. + return 0; +}; + /** * Move the flyout to the edge of the workspace. */ @@ -140,36 +197,9 @@ Blockly.VerticalFlyout.prototype.position = function() { var edgeHeight = targetWorkspaceMetrics.viewHeight - 2 * this.CORNER_RADIUS; this.setBackgroundPath_(edgeWidth, edgeHeight); - // Y is always 0 since this is a vertical flyout. - var y = 0; - // If this flyout is the toolbox flyout. - if (this.targetWorkspace.toolboxPosition == this.toolboxPosition_) { - // If there is a category toolbox. - if (targetWorkspaceMetrics.toolboxWidth) { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { - var x = targetWorkspaceMetrics.toolboxWidth; - } else { - var x = targetWorkspaceMetrics.viewWidth - this.width_; - } - } else { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { - var x = 0; - } else { - var x = targetWorkspaceMetrics.viewWidth; - } - } - } else { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { - var x = 0; - } else { - // Because the anchor point of the flyout is on the left, but we want - // 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. - var x = targetWorkspaceMetrics.viewWidth + - targetWorkspaceMetrics.absoluteLeft - this.width_; - } - } + var x = this.getX(); + var y = this.getY(); + this.positionAt_(this.width_, this.height_, x, y); }; diff --git a/core/interfaces/i_flyout.js b/core/interfaces/i_flyout.js index 37ffeaba3..f3a91e556 100644 --- a/core/interfaces/i_flyout.js +++ b/core/interfaces/i_flyout.js @@ -161,6 +161,18 @@ Blockly.IFlyout.prototype.reflow; */ Blockly.IFlyout.prototype.isScrollable; +/** + * Calculates the x coordinate for the flyout position. + * @return {number} X coordinate. + */ +Blockly.IFlyout.prototype.getX; + +/** + * Calculates the y coordinate for the flyout position. + * @return {number} Y coordinate. + */ +Blockly.IFlyout.prototype.getY; + /** * Position the flyout. * @return {void} diff --git a/tests/mocha/flyout_test.js b/tests/mocha/flyout_test.js index 840e48379..ff6d200fa 100644 --- a/tests/mocha/flyout_test.js +++ b/tests/mocha/flyout_test.js @@ -29,6 +29,176 @@ suite('Flyout', function() { sharedTestTeardown.call(this); }); + suite('position', function() { + suite('vertical flyout', function() { + suite('simple flyout', function() { + setup(function() { + this.flyout = this.workspace.getFlyout(); + }); + 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, + }); + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_RIGHT; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_RIGHT; + chai.assert.equal(this.flyout.getX(), 100, 'x should be right of workspace'); + }); + test('x is 0 if flyout at left', function() { + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_LEFT; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_LEFT; + chai.assert.equal(this.flyout.getX(), 0, 'x should be 0 if the flyout is on the left'); + }); + }); + suite('toolbox flyout', function() { + setup(function() { + var toolbox = document.getElementById('toolbox-categories'); + this.workspace = Blockly.inject('blocklyDiv', + { + toolbox: toolbox + }); + this.flyout = this.workspace.getToolbox().getFlyout(); + }); + 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, + }); + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_LEFT; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_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, + }); + this.flyout.width_ = 10; + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_RIGHT; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_RIGHT; + chai.assert.equal(this.flyout.getX(), 90,'x + width should be aligned with toolbox'); + }); + }); + // These tests simulate a trashcan flyout, i.e. the flyout under test is on the + // opposite side of the workspace toolbox setting. + suite('trashcan flyout', function() { + setup(function() { + this.flyout = this.workspace.getFlyout(); + }); + test('x is 0 if trashcan on left', function() { + sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ + viewWidth: 100, + }); + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_RIGHT; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_LEFT; + chai.assert.equal(this.flyout.getX(), 0, 'x should be aligned with left edge'); + }); + 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, + }); + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_LEFT; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_RIGHT; + chai.assert.equal(this.flyout.getX(), 90, 'x + width should be aligned with right edge'); + }); + }); + }); + + suite('horizontal flyout', function() { + setup(function() { + this.workspace = Blockly.inject('blocklyDiv', + { + toolbox: this.toolboxXml, + horizontalLayout: true, + }); + }); + teardown(function() { + workspaceTeardown.call(this, this.workspace); + }); + suite('simple flyout', function() { + setup(function() { + this.flyout = this.workspace.getFlyout(); + }); + test('x is always 0', function() { + chai.assert.equal(this.flyout.getX(), 0, 'x coordinate in horizontal flyout should be 0'); + }); + test('y is 0 if flyout at top', function() { + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_TOP; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_TOP; + chai.assert.equal(this.flyout.getY(), 0, 'y should be 0 if flyout is at the top'); + }); + test('y is below workspace if flyout at bottom', function() { + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_BOTTOM; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_BOTTOM; + sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ + viewHeight: 50, + }); + chai.assert.equal(this.flyout.getY(), 50, 'y should be below the workspace'); + }); + }); + suite('toolbox flyout', function() { + setup(function() { + var toolbox = document.getElementById('toolbox-categories'); + this.workspace = Blockly.inject('blocklyDiv', + { + toolbox: toolbox, + horizontalLayout: true, + }); + this.flyout = this.workspace.getToolbox().getFlyout(); + }); + 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, + }); + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_TOP; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_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, + }); + this.flyout.height_ = 30; + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_BOTTOM; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_BOTTOM; + chai.assert.equal(this.flyout.getY(), 70, 'y + height should be aligned with toolbox'); + }); + }); + // These tests simulate a trashcan flyout, i.e. the flyout under test is on the + // opposite side of the workspace toolbox setting. + suite('trashcan flyout', function() { + setup(function() { + this.flyout = this.workspace.getFlyout(); + }); + test('y is 0 if trashcan at top', function() { + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_BOTTOM; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_TOP; + chai.assert.equal(this.flyout.getY(), 0, 'y should be aligned with top'); + }); + test('trashcan on bottom covers bottom of workspace', function() { + this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_TOP; + this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_BOTTOM; + sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({ + viewHeight: 50, + absoluteTop: 10, + }); + this.flyout.height_ = 20; + chai.assert.equal(this.flyout.getY(), 40, 'y + height should be aligned with bottom'); + }); + }); + }); + }); + suite('createFlyoutInfo_', function() { setup(function() { this.simpleToolboxJSON = getSimpleJSON();