Unify code for positioning the widget div (#1334)

* New widget div functions, used in context menu code

* Make all widget div positioning functions use the same argument order

* Use new widget div functions for fields

* share code for measuring menu size

* Get rid of positionMenu

* Update copyright date

* Rebuild blockly_uncompressed because there's a new require in town
This commit is contained in:
Rachel Fenichel
2017-09-22 16:07:32 -07:00
committed by GitHub
parent 6a1f30bd12
commit acd3a00f4a
9 changed files with 283 additions and 130 deletions

View File

@@ -42,7 +42,7 @@ goog.addDependency("../../../" + dir + "/core/block.js", ['Blockly.Block'], ['Bl
goog.addDependency("../../../" + dir + "/core/block_drag_surface.js", ['Blockly.BlockDragSurfaceSvg'], ['Blockly.utils', 'goog.asserts', 'goog.math.Coordinate']);
goog.addDependency("../../../" + dir + "/core/block_dragger.js", ['Blockly.BlockDragger'], ['Blockly.DraggedConnectionManager', 'goog.math.Coordinate', 'goog.asserts']);
goog.addDependency("../../../" + dir + "/core/block_render_svg.js", ['Blockly.BlockSvg.render'], ['Blockly.BlockSvg', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.Block', 'Blockly.ContextMenu', 'Blockly.Grid', 'Blockly.RenderedConnection', 'Blockly.Touch', 'Blockly.utils', 'goog.Timer', 'goog.asserts', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.Block', 'Blockly.ContextMenu', 'Blockly.Grid', 'Blockly.RenderedConnection', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.utils', 'goog.Timer', 'goog.asserts', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/blockly.js", ['Blockly'], ['Blockly.BlockSvg.render', 'Blockly.Events', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldTextInput', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.Generator', 'Blockly.Msg', 'Blockly.Procedures', 'Blockly.Toolbox', 'Blockly.Touch', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.constants', 'Blockly.inject', 'Blockly.utils', 'goog.color', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/blocks.js", ['Blockly.Blocks'], []);
goog.addDependency("../../../" + dir + "/core/bubble.js", ['Blockly.Bubble'], ['Blockly.Touch', 'Blockly.Workspace', 'goog.dom', 'goog.math', 'goog.math.Coordinate', 'goog.userAgent']);
@@ -50,7 +50,7 @@ goog.addDependency("../../../" + dir + "/core/comment.js", ['Blockly.Comment'],
goog.addDependency("../../../" + dir + "/core/connection.js", ['Blockly.Connection'], ['goog.asserts', 'goog.dom']);
goog.addDependency("../../../" + dir + "/core/connection_db.js", ['Blockly.ConnectionDB'], ['Blockly.Connection']);
goog.addDependency("../../../" + dir + "/core/constants.js", ['Blockly.constants'], []);
goog.addDependency("../../../" + dir + "/core/contextmenu.js", ['Blockly.ContextMenu'], ['goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem']);
goog.addDependency("../../../" + dir + "/core/contextmenu.js", ['Blockly.ContextMenu'], ['Blockly.utils', 'Blockly.utils.uiMenu', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem']);
goog.addDependency("../../../" + dir + "/core/css.js", ['Blockly.Css'], []);
goog.addDependency("../../../" + dir + "/core/dragged_connection_manager.js", ['Blockly.DraggedConnectionManager'], ['Blockly.RenderedConnection', 'goog.math.Coordinate']);
goog.addDependency("../../../" + dir + "/core/events.js", ['Blockly.Events'], ['goog.array', 'goog.math.Coordinate']);
@@ -58,9 +58,9 @@ goog.addDependency("../../../" + dir + "/core/extensions.js", ['Blockly.Extensio
goog.addDependency("../../../" + dir + "/core/field.js", ['Blockly.Field'], ['Blockly.Gesture', 'goog.asserts', 'goog.dom', 'goog.math.Size', 'goog.style', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/field_angle.js", ['Blockly.FieldAngle'], ['Blockly.FieldTextInput', 'goog.math', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/field_checkbox.js", ['Blockly.FieldCheckbox'], ['Blockly.Field']);
goog.addDependency("../../../" + dir + "/core/field_colour.js", ['Blockly.FieldColour'], ['Blockly.Field', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.ColorPicker']);
goog.addDependency("../../../" + dir + "/core/field_date.js", ['Blockly.FieldDate'], ['Blockly.Field', 'goog.date', 'goog.dom', 'goog.events', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_he', 'goog.style', 'goog.ui.DatePicker']);
goog.addDependency("../../../" + dir + "/core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Field', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/field_colour.js", ['Blockly.FieldColour'], ['Blockly.Field', 'Blockly.utils', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.ColorPicker']);
goog.addDependency("../../../" + dir + "/core/field_date.js", ['Blockly.FieldDate'], ['Blockly.Field', 'Blockly.utils', 'goog.date', 'goog.dom', 'goog.events', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_he', 'goog.style', 'goog.ui.DatePicker']);
goog.addDependency("../../../" + dir + "/core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Field', 'Blockly.utils', 'Blockly.utils.uiMenu', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/field_image.js", ['Blockly.FieldImage'], ['Blockly.Field', 'goog.dom', 'goog.math.Size', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/field_label.js", ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.Tooltip', 'goog.dom', 'goog.math.Size']);
goog.addDependency("../../../" + dir + "/core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'goog.math']);
@@ -88,6 +88,7 @@ goog.addDependency("../../../" + dir + "/core/toolbox.js", ['Blockly.Toolbox'],
goog.addDependency("../../../" + dir + "/core/tooltip.js", ['Blockly.Tooltip'], ['goog.dom', 'goog.dom.TagName']);
goog.addDependency("../../../" + dir + "/core/touch.js", ['Blockly.Touch'], ['goog.events', 'goog.events.BrowserFeature', 'goog.string']);
goog.addDependency("../../../" + dir + "/core/trashcan.js", ['Blockly.Trashcan'], ['goog.Timer', 'goog.dom', 'goog.math', 'goog.math.Rect']);
goog.addDependency("../../../" + dir + "/core/ui_menu_utils.js", ['Blockly.utils.uiMenu'], []);
goog.addDependency("../../../" + dir + "/core/utils.js", ['Blockly.utils'], ['Blockly.Touch', 'goog.dom', 'goog.events.BrowserFeature', 'goog.math.Coordinate', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/variable_map.js", ['Blockly.VariableMap'], []);
goog.addDependency("../../../" + dir + "/core/variable_model.js", ['Blockly.VariableModel'], ['goog.string']);
@@ -1729,6 +1730,7 @@ goog.require('Blockly.ZoomControls');
goog.require('Blockly.constants');
goog.require('Blockly.inject');
goog.require('Blockly.utils');
goog.require('Blockly.utils.uiMenu');
delete this.BLOCKLY_DIR;
delete this.BLOCKLY_BOOT;

View File

@@ -30,6 +30,9 @@
*/
goog.provide('Blockly.ContextMenu');
goog.require('Blockly.utils');
goog.require('Blockly.utils.uiMenu');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.style');
@@ -115,37 +118,29 @@ Blockly.ContextMenu.populate_ = function(options, rtl) {
* @private
*/
Blockly.ContextMenu.position_ = function(menu, e, rtl) {
// Record windowSize and scrollOffset before adding menu.
var windowSize = goog.dom.getViewportSize();
var scrollOffset = goog.style.getViewportPageOffset(document);
// Record windowSize and scrollOffset before adding menu.
var viewportBBox = Blockly.utils.getViewportBBox();
// This one is just a point, but we'll pretend that it's a rect so we can use
// some helper functions.
var anchorBBox = {
top: e.clientY + viewportBBox.top,
bottom: e.clientY + viewportBBox.top,
left: e.clientX + viewportBBox.left,
right: e.clientX + viewportBBox.left
};
Blockly.ContextMenu.createWidget_(menu);
var menuDom = menu.getElement();
// Record menuSize after adding menu.
var menuSize = goog.style.getSize(menuDom);
var menuSize = Blockly.utils.uiMenu.getSize(menu);
// Position the menu.
var x = e.clientX + scrollOffset.x;
var y = e.clientY + scrollOffset.y;
// Flip menu vertically if off the bottom.
if (e.clientY + menuSize.height >= windowSize.height) {
y -= menuSize.height;
}
// Flip menu horizontally if off the edge.
if (rtl) {
if (menuSize.width >= e.clientX) {
x += menuSize.width;
}
} else {
if (e.clientX + menuSize.width >= windowSize.width) {
x -= menuSize.width;
}
Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize);
}
Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl);
Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize, rtl);
// Calling menuDom.focus() has to wait until after the menu has been placed
// correctly. Otherwise it will cause a page scroll to get the misplaced menu
// in view. See issue #1329.
menuDom.focus();
menu.getElement().focus();
};
/**

View File

@@ -405,16 +405,23 @@ Blockly.Field.prototype.getSize = function() {
};
/**
* Returns the height and width of the field,
* accounting for the workspace scaling.
* @return {!goog.math.Size} Height and width.
* Returns the bounding box of the rendered field, accounting for workspace
* scaling.
* @return {!Object} An object with top, bottom, left, and right in pixels
* relative to the top left corner of the page (window coordinates).
* @private
*/
Blockly.Field.prototype.getScaledBBox_ = function() {
var bBox = this.borderRect_.getBBox();
// Create new object, as getBBox can return an uneditable SVGRect in IE.
return new goog.math.Size(bBox.width * this.sourceBlock_.workspace.scale,
bBox.height * this.sourceBlock_.workspace.scale);
var scaledHeight = bBox.height * this.sourceBlock_.workspace.scale;
var scaledWidth = bBox.width * this.sourceBlock_.workspace.scale;
var xy = this.getAbsoluteXY_();
return {
top: xy.y,
bottom: xy.y + scaledHeight,
left: xy.x,
right: xy.x + scaledWidth
};
};
/**

View File

@@ -27,6 +27,8 @@
goog.provide('Blockly.FieldColour');
goog.require('Blockly.Field');
goog.require('Blockly.utils');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.style');
@@ -167,38 +169,17 @@ Blockly.FieldColour.prototype.showEditor_ = function() {
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL,
Blockly.FieldColour.widgetDispose_);
// Position the palette to line up with the field.
// Record windowSize and scrollOffset before adding the palette.
var windowSize = goog.dom.getViewportSize();
var scrollOffset = goog.style.getViewportPageOffset(document);
var xy = this.getAbsoluteXY_();
var borderBBox = this.getScaledBBox_();
// Record paletteSize after adding the palette.
// Record viewport dimensions before adding the widget.
var viewportBBox = Blockly.utils.getViewportBBox();
var anchorBBox = this.getScaledBBox_();
// Create and add the colour picker, then record the size.
var picker = this.createWidget_();
var paletteSize = goog.style.getSize(picker.getElement());
// Flip the palette vertically if off the bottom.
if (xy.y + paletteSize.height + borderBBox.height >=
windowSize.height + scrollOffset.y) {
xy.y -= paletteSize.height - 1;
} else {
xy.y += borderBBox.height - 1;
}
if (this.sourceBlock_.RTL) {
xy.x += borderBBox.width;
xy.x -= paletteSize.width;
// Don't go offscreen left.
if (xy.x < scrollOffset.x) {
xy.x = scrollOffset.x;
}
} else {
// Don't go offscreen right.
if (xy.x > windowSize.width + scrollOffset.x - paletteSize.width) {
xy.x = windowSize.width + scrollOffset.x - paletteSize.width;
}
}
Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset,
this.sourceBlock_.RTL);
// Position the picker to line up with the field.
Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, paletteSize,
this.sourceBlock_.RTL);
// Configure event handler.
var thisField = this;

View File

@@ -27,6 +27,8 @@
goog.provide('Blockly.FieldDate');
goog.require('Blockly.Field');
goog.require('Blockly.utils');
goog.require('goog.date');
goog.require('goog.dom');
goog.require('goog.events');
@@ -102,38 +104,17 @@ Blockly.FieldDate.prototype.showEditor_ = function() {
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL,
Blockly.FieldDate.widgetDispose_);
// Position the picker to line up with the field.
// Record windowSize and scrollOffset before adding the picker.
var windowSize = goog.dom.getViewportSize();
var scrollOffset = goog.style.getViewportPageOffset(document);
var xy = this.getAbsoluteXY_();
var borderBBox = this.getScaledBBox_();
// Record viewport dimensions before adding the picker.
var viewportBBox = Blockly.utils.getViewportBBox();
var anchorBBox = this.getScaledBBox_();
// Create and add the date picker, then record the size.
var picker = this.createWidget_();
// Record pickerSize after adding the date picker.
var pickerSize = goog.style.getSize(picker.getElement());
// Flip the picker vertically if off the bottom.
if (xy.y + pickerSize.height + borderBBox.height >=
windowSize.height + scrollOffset.y) {
xy.y -= pickerSize.height - 1;
} else {
xy.y += borderBBox.height - 1;
}
if (this.sourceBlock_.RTL) {
xy.x += borderBBox.width;
xy.x -= pickerSize.width;
// Don't go offscreen left.
if (xy.x < scrollOffset.x) {
xy.x = scrollOffset.x;
}
} else {
// Don't go offscreen right.
if (xy.x > windowSize.width + scrollOffset.x - pickerSize.width) {
xy.x = windowSize.width + scrollOffset.x - pickerSize.width;
}
}
Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset,
this.sourceBlock_.RTL);
// Position the picker to line up with the field.
Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, pickerSize,
this.sourceBlock_.RTL);
// Configure event handler.
var thisField = this;

View File

@@ -29,6 +29,9 @@
goog.provide('Blockly.FieldDropdown');
goog.require('Blockly.Field');
goog.require('Blockly.utils');
goog.require('Blockly.utils.uiMenu');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.style');
@@ -231,44 +234,23 @@ Blockly.FieldDropdown.prototype.createMenu_ = function() {
* @private
*/
Blockly.FieldDropdown.prototype.positionMenu_ = function(menu) {
// Record windowSize and scrollOffset before adding menu.
var windowSize = goog.dom.getViewportSize();
var scrollOffset = goog.style.getViewportPageOffset(document);
var xy = this.getAbsoluteXY_();
var borderBBox = this.getScaledBBox_();
// Record viewport dimensions before adding the dropdown.
var viewportBBox = Blockly.utils.getViewportBBox();
var anchorBBox = this.getAnchorDimensions_();
this.createWidget_(menu);
var menuDom = menu.getElement();
// Record menuSize after adding menu.
var menuSize = goog.style.getSize(menuDom);
// Recalculate height for the total content, not only box height.
menuSize.height = menuDom.scrollHeight;
var menuSize = Blockly.utils.uiMenu.getSize(menu);
// Position the menu.
// Flip menu vertically if off the bottom.
if (xy.y + menuSize.height + borderBBox.height >=
windowSize.height + scrollOffset.y) {
xy.y -= menuSize.height + 2;
} else {
xy.y += borderBBox.height;
}
if (this.sourceBlock_.RTL) {
xy.x += borderBBox.width;
xy.x += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
// Don't go offscreen left.
if (xy.x < scrollOffset.x + menuSize.width) {
xy.x = scrollOffset.x + menuSize.width;
}
} else {
xy.x -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
// Don't go offscreen right.
if (xy.x > windowSize.width + scrollOffset.x - menuSize.width) {
xy.x = windowSize.width + scrollOffset.x - menuSize.width;
}
Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize);
}
Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset,
this.sourceBlock_.RTL);
menuDom.focus();
// Position the menu.
Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize,
this.sourceBlock_.RTL);
// Calling menuDom.focus() has to wait until after the menu has been placed
// correctly. Otherwise it will cause a page scroll to get the misplaced menu
// in view. See issue #1329.
menu.getElement().focus();
};
/**
@@ -284,6 +266,27 @@ Blockly.FieldDropdown.prototype.createWidget_ = function(menu) {
menu.setAllowAutoFocus(true);
};
/**
* Returns the coordinates of the anchor rectangle for the widget div.
* On a FieldDropdown we take the top-left corner of the field, then adjust for
* the size of the checkmark that is displayed next to the currently selected
* item. This means that the item text will be positioned directly under the
* field text, rather than offset slightly.
* @returns {!Object} The bounding rectangle of the anchor, in window
* coordinates.
* @private
*/
Blockly.FieldDropdown.prototype.getAnchorDimensions_ = function() {
var boundingBox = this.getScaledBBox_();
if (this.sourceBlock_.RTL) {
boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
} else {
boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
}
return boundingBox;
};
/**
* Handle the selection of an item in the dropdown menu.
* @param {!goog.ui.Menu} menu The Menu component clicked.

67
core/ui_menu_utils.js Normal file
View File

@@ -0,0 +1,67 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Utility methods for working with the closure menu (goog.ui.menu).
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
/**
* @name Blockly.utils.uiMenu
* @namespace
**/
goog.provide('Blockly.utils.uiMenu');
/**
* Get the size of a rendered goog.ui.Menu.
* @param {!goog.ui.Menu} menu The menu to measure.
* @return {!goog.math.Size} Object with width and height properties.
* @package
*/
Blockly.utils.uiMenu.getSize = function(menu) {
var menuDom = menu.getElement();
var menuSize = goog.style.getSize(menuDom);
// Recalculate height for the total content, not only box height.
menuSize.height = menuDom.scrollHeight;
return menuSize;
};
/**
* Adjust the bounding boxes used to position the widget div to deal with RTL
* goog.ui.Menu positioning. In RTL mode the menu renders down and to the left
* of its start point, instead of down and to the right. Adjusting all of the
* bounding boxes accordingly allows us to use the same code for all widgets.
* This function in-place modifies the provided bounding boxes.
* @param {!Object} viewportBBox The bounding rectangle of the current viewport,
* in window coordinates.
* @param {!Object} anchorBBox The bounding rectangle of the anchor, in window
* coordinates.
* @param {!goog.math.Size} menuSize The size of the menu that is inside the
* widget div, in window coordinates.
* @package
*/
Blockly.utils.uiMenu.adjustBBoxesForRTL = function(viewportBBox, anchorBBox,
menuSize) {
anchorBBox.left += menuSize.width;
anchorBBox.right += menuSize.width;
viewportBBox.left += menuSize.width;
viewportBBox.right += menuSize.width;
};

View File

@@ -917,3 +917,24 @@ Blockly.utils.setCssTransform = function(node, transform) {
node.style['transform'] = transform;
node.style['-webkit-transform'] = transform;
};
/**
* Get the position of the current viewport in window coordinates. This takes
* scroll into account.
* @return {!Object} an object containing window width, height, and scroll
* position in window coordinates.
* @package
*/
Blockly.utils.getViewportBBox = function() {
// Pixels.
var windowSize = goog.dom.getViewportSize();
// Pixels, in window coordinates.
var scrollOffset = goog.style.getViewportPageOffset(document);
return {
right: windowSize.width + scrollOffset.x,
bottom: windowSize.height + scrollOffset.y,
top: scrollOffset.y,
left: scrollOffset.x
};
};

View File

@@ -127,8 +127,8 @@ Blockly.WidgetDiv.hideIfOwner = function(oldOwner) {
/**
* Position the widget at a given location. Prevent the widget from going
* offscreen top or left (right in RTL).
* @param {number} anchorX Horizontal location (window coorditates, not body).
* @param {number} anchorY Vertical location (window coorditates, not body).
* @param {number} anchorX Horizontal location (window coordinates, not body).
* @param {number} anchorY Vertical location (window coordinates, not body).
* @param {!goog.math.Size} windowSize Height/width of window.
* @param {!goog.math.Coordinate} scrollOffset X/y of window scrollbars.
* @param {boolean} rtl True if RTL, false if LTR.
@@ -150,7 +150,103 @@ Blockly.WidgetDiv.position = function(anchorX, anchorY, windowSize,
anchorX = scrollOffset.x;
}
}
Blockly.WidgetDiv.DIV.style.left = anchorX + 'px';
Blockly.WidgetDiv.DIV.style.top = anchorY + 'px';
Blockly.WidgetDiv.DIV.style.height = windowSize.height + 'px';
Blockly.WidgetDiv.positionInternal_(anchorX, anchorY, windowSize.height);
};
/**
* Set the widget div's position and height. This function does nothing clever:
* it will not ensure that your widget div ends up in the visible window.
* @param {number} x Horizontal location (window coordinates, not body).
* @param {number} y Vertical location (window coordinates, not body).
* @param {number} height The height of the widget div (pixels).
* @private
*/
Blockly.WidgetDiv.positionInternal_ = function(x, y, height) {
Blockly.WidgetDiv.DIV.style.left = x + 'px';
Blockly.WidgetDiv.DIV.style.top = y + 'px';
Blockly.WidgetDiv.DIV.style.height = height + 'px';
};
/**
* Position the widget div based on an anchor rectangle.
* The widget should be placed adjacent to but not overlapping the anchor
* rectangle. The preferred position is directly below and aligned to the left
* (ltr) or right (rtl) side of the anchor.
* @param {!Object} viewportBBox The bounding rectangle of the current viewport,
* in window coordinates.
* @param {!Object} anchorBBox The bounding rectangle of the anchor, in window
* coordinates.
* @param {!goog.math.Size} widgetSize The size of the widget that is inside the
* widget div, in window coordinates.
* @param {boolean} rtl Whether the workspace is in RTL mode. This determines
* horizontal alignment.
* @package
*/
Blockly.WidgetDiv.positionWithAnchor = function(viewportBBox, anchorBBox,
widgetSize, rtl) {
var y = Blockly.WidgetDiv.calculateY_(viewportBBox, anchorBBox, widgetSize);
var x = Blockly.WidgetDiv.calculateX_(viewportBBox, anchorBBox, widgetSize,
rtl);
Blockly.WidgetDiv.positionInternal_(x, y, widgetSize.height);
};
/**
* Calculate an x position (in window coordinates) such that the widget will not
* be offscreen on the right or left.
* @param {!Object} viewportBBox The bounding rectangle of the current viewport,
* in window coordinates.
* @param {!Object} anchorBBox The bounding rectangle of the anchor, in window
* coordinates.
* @param {goog.math.Size} widgetSize The dimensions of the widget inside the
* widget div.
* @param {boolean} rtl Whether the Blockly workspace is in RTL mode.
* @return {number} A valid x-coordinate for the top left corner of the widget
* div, in window coordinates.
* @private
*/
Blockly.WidgetDiv.calculateX_ = function(viewportBBox, anchorBBox, widgetSize,
rtl) {
if (rtl) {
// Try to align the right side of the field and the right side of the widget.
var widgetLeft = anchorBBox.right - widgetSize.width;
// Don't go offscreen left.
var x = Math.max(widgetLeft, viewportBBox.left);
// But really don't go offscreen right:
return Math.min(x, viewportBBox.right - widgetSize.width);
} else {
// Try to align the left side of the field and the left side of the widget.
// Don't go offscreen right.
var x = Math.min(anchorBBox.left,
viewportBBox.right - widgetSize.width);
// But left is more important, because that's where the text is.
return Math.max(x, viewportBBox.left);
}
};
/**
* Calculate a y position (in window coordinates) such that the widget will not
* be offscreen on the top or bottom.
* @param {!Object} viewportBBox The bounding rectangle of the current viewport,
* in window coordinates.
* @param {!Object} anchorBBox The bounding rectangle of the anchor, in window
* coordinates.
* @param {goog.math.Size} widgetSize The dimensions of the widget inside the
* widget div.
* @return {number} A valid y-coordinate for the top left corner of the widget
* div, in window coordinates.
* @private
*/
Blockly.WidgetDiv.calculateY_ = function(viewportBBox, anchorBBox, widgetSize) {
// Flip the widget vertically if off the bottom.
if (anchorBBox.bottom + widgetSize.height >=
viewportBBox.bottom) {
// The bottom of the widget is at the top of the field.
return anchorBBox.top - widgetSize.height;
// The widget could go off the top of the window, but it would also go off
// the bottom. The window is just too small.
} else {
// The top of the widget is at the bottom of the field.
return anchorBBox.bottom;
}
};