Straw-man implementation of text drop-downs (#407)

* Refactor drop-down positioning to encapsulate block positioning

Moves block-positioning logic to the DropDownDiv class. Since icon menus use a special offset for the secondary mode, add an option for that.

* Swap out field_dropdown's use of WidgetDiv with DropDownDiv

* Update CSS for goog-menus in DropDownDivs

* Fix hiding behavior when showEditor_ is called again
This commit is contained in:
Tim Mickel 2016-06-14 17:08:14 -04:00 committed by GitHub
parent 7c35d4e6f5
commit 25e80c2d09
4 changed files with 108 additions and 63 deletions

View file

@ -739,6 +739,13 @@ Blockly.Css.CONTENT = [
'z-index: 20000;', /* Arbitrary, but some apps depend on it... */
'}',
'.blocklyDropDownDiv .goog-menu {',
'cursor: default;',
'font: normal 13px Arial, sans-serif;',
'outline: none;',
'z-index: 20000;', /* Arbitrary, but some apps depend on it... */
'}',
/* Copied from: goog/css/menuitem.css */
/*
* Copyright 2009 The Closure Library Authors. All Rights Reserved.
@ -778,9 +785,21 @@ Blockly.Css.CONTENT = [
'white-space: nowrap;',
'}',
'.blocklyDropDownDiv .goog-menuitem {',
'color: #fff;',
'font: normal 13px Arial, sans-serif;',
'font-weight: bold;',
'list-style: none;',
'margin: 0;',
/* 28px on the left for icon or checkbox; 7em on the right for shortcut. */
'padding: 4px 7em 4px 28px;',
'white-space: nowrap;',
'}',
/* BiDi override for the resting state. */
/* #noflip */
'.blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {',
'.blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl, ',
'.blocklyDropDownDiv .goog-menuitem.goog-menuitem-rtl {',
/* Flip left/right padding for BiDi. */
'padding-left: 7em;',
'padding-right: 28px;',
@ -788,7 +807,9 @@ Blockly.Css.CONTENT = [
/* If a menu doesn't have checkable items or items with icons, remove padding. */
'.blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,',
'.blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {',
'.blocklyWidgetDiv .goog-menu-noicon .goog-menuitem, ',
'.blocklyDropDownDiv .goog-menu-nocheckbox .goog-menuitem,',
'.blocklyDropDownDiv .goog-menu-noicon .goog-menuitem { ',
'padding-left: 12px;',
'}',
@ -796,22 +817,27 @@ Blockly.Css.CONTENT = [
* If a menu doesn't have items with shortcuts, leave just enough room for
* submenu arrows, if they are rendered.
*/
'.blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {',
'.blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem, ',
'.blocklyDropDownDiv .goog-menu-noaccel .goog-menuitem {',
'padding-right: 20px;',
'}',
'.blocklyWidgetDiv .goog-menuitem-content {',
'.blocklyWidgetDiv .goog-menuitem-content ',
'.blocklyDropDownDiv .goog-menuitem-content {',
'color: #000;',
'font: normal 13px Arial, sans-serif;',
'}',
/* State: disabled. */
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,',
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {',
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content ',
'.blocklyDropDownDiv .goog-menuitem-disabled .goog-menuitem-accel,',
'.blocklyDropDownDiv .goog-menuitem-disabled .goog-menuitem-content {',
'color: #ccc !important;',
'}',
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {',
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon, ',
'.blocklyDropDownDiv .goog-menuitem-disabled .goog-menuitem-icon {',
'opacity: 0.3;',
'-moz-opacity: 0.3;',
'filter: alpha(opacity=30);',
@ -830,9 +856,16 @@ Blockly.Css.CONTENT = [
'padding-top: 3px;',
'}',
'.blocklyDropDownDiv .goog-menuitem-highlight,',
'.blocklyDropDownDiv .goog-menuitem-hover {',
'background-color: rgba(0, 0, 0, 0.2);',
'}',
/* State: selected/checked. */
'.blocklyWidgetDiv .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-menuitem-icon {',
'.blocklyWidgetDiv .goog-menuitem-icon, ',
'.blocklyDropDownDiv .goog-menuitem-checkbox,',
'.blocklyDropDownDiv .goog-menuitem-icon {',
'background-repeat: no-repeat;',
'height: 16px;',
'left: 6px;',
@ -845,20 +878,25 @@ Blockly.Css.CONTENT = [
/* BiDi override for the selected/checked state. */
/* #noflip */
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {',
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon,',
'.blocklyDropDownDiv .goog-menuitem-rtl .goog-menuitem-checkbox,',
'.blocklyDropDownDiv .goog-menuitem-rtl .goog-menuitem-icon {',
/* Flip left/right positioning. */
'left: auto;',
'right: 6px;',
'}',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon,',
'.blocklyDropDownDiv .goog-option-selected .goog-menuitem-checkbox,',
'.blocklyDropDownDiv .goog-option-selected .goog-menuitem-icon {',
/* Client apps may override the URL at which they serve the sprite. */
'background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;',
'background: url(<<<PATH>>>/sprites.png) no-repeat -48px -16px !important;',
'}',
/* Keyboard shortcut ("accelerator") style. */
'.blocklyWidgetDiv .goog-menuitem-accel {',
'.blocklyWidgetDiv .goog-menuitem-accel, ',
'.blocklyDropDownDiv .goog-menuitem-accel {',
'color: #999;',
/* Keyboard shortcuts are untranslated; always left-to-right. */
/* #noflip */
@ -872,7 +910,8 @@ Blockly.Css.CONTENT = [
/* BiDi override for shortcut style. */
/* #noflip */
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {',
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel, ',
'.blocklyDropDownDiv .goog-menuitem-rtl .goog-menuitem-accel {',
/* Flip left/right positioning and text alignment. */
'left: 0;',
'right: auto;',
@ -880,11 +919,13 @@ Blockly.Css.CONTENT = [
'}',
/* Mnemonic styles. */
'.blocklyWidgetDiv .goog-menuitem-mnemonic-hint {',
'.blocklyWidgetDiv .goog-menuitem-mnemonic-hint, ',
'.blocklyDropDownDiv .goog-menuitem-mnemonic-hint {',
'text-decoration: underline;',
'}',
'.blocklyWidgetDiv .goog-menuitem-mnemonic-separator {',
'.blocklyWidgetDiv .goog-menuitem-mnemonic-separator, ',
'.blocklyDropDownDiv .goog-menuitem-mnemonic-separator {',
'color: #999;',
'font-size: 12px;',
'padding-left: 4px;',
@ -904,7 +945,8 @@ Blockly.Css.CONTENT = [
* @author attila@google.com (Attila Bodis)
*/
'.blocklyWidgetDiv .goog-menuseparator {',
'.blocklyWidgetDiv .goog-menuseparator, ',
'.blocklyDropDownDiv .goog-menuseparator {',
'border-top: 1px solid #ccc;',
'margin: 4px 0;',
'padding: 0;',

View file

@ -165,6 +165,38 @@ Blockly.DropDownDiv.setColour = function(backgroundColour, borderColour) {
Blockly.DropDownDiv.arrow_.style.borderColor = borderColour;
};
/**
* Shortcut to show and place the drop-down with positioning determined
* by a particular block. The primary position will be below the block,
* and the secondary position above the block. Drop-down will be
* constrained to the block's workspace.
* @param {Object} owner The object showing the drop-down
* @param {!Blockly.Block} block Block to position the drop-down around.
* @param {Function=} opt_onHide Optional callback for when the drop-down is hidden.
* @param {Number} opt_secondaryYOffset Optional Y offset for above-block positioning.
* @return {boolean} True if the menu rendered below block; false if above.
*/
Blockly.DropDownDiv.showPositionedByBlock = function(owner, block,
opt_onHide, opt_secondaryYOffset) {
var scale = block.workspace.scale;
var bBox = block.getHeightWidth();
bBox.width *= scale;
bBox.height *= scale;
var position = goog.style.getPageOffset(block.getSvgRoot());
// If we can fit it, render below the block.
var primaryX = position.x + bBox.width / 2;
var primaryY = position.y + bBox.height;
// If we can't fit it, render above the entire parent block.
var secondaryX = primaryX;
var secondaryY = position.y;
if (opt_secondaryYOffset) {
secondaryY += opt_secondaryYOffset;
}
// Set bounds to workspace; show the drop-down.
Blockly.DropDownDiv.setBoundsElement(block.workspace.getParentSvg().parentNode);
return Blockly.DropDownDiv.show(this, primaryX, primaryY, secondaryX, secondaryY, opt_onHide);
};
/**
* Show and place the drop-down.
* The drop-down is placed with an absolute "origin point" (x, y) - i.e.,

View file

@ -29,6 +29,7 @@
goog.provide('Blockly.FieldDropdown');
goog.require('Blockly.Field');
goog.require('Blockly.DropDownDiv');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.style');
@ -101,7 +102,12 @@ Blockly.FieldDropdown.prototype.init = function() {
* @private
*/
Blockly.FieldDropdown.prototype.showEditor_ = function() {
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, null);
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
Blockly.DropDownDiv.hideWithoutAnimation();
Blockly.DropDownDiv.clearContent();
var contentDiv = Blockly.DropDownDiv.getContentDiv();
var thisField = this;
function callback(e) {
@ -119,7 +125,7 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
thisField.setValue(value);
}
}
Blockly.WidgetDiv.hideIfOwner(thisField);
Blockly.DropDownDiv.hide();
}
var menu = new goog.ui.Menu();
@ -154,12 +160,7 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
callbackTouchEnd);
// 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_();
var div = Blockly.WidgetDiv.DIV;
menu.render(div);
menu.render(contentDiv);
var menuDom = menu.getElement();
Blockly.addClass_(menuDom, 'blocklyDropdownMenu');
// Record menuSize after adding menu.
@ -167,30 +168,9 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
// Recalculate height for the total content, not only box height.
menuSize.height = menuDom.scrollHeight;
// 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.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset,
this.sourceBlock_.RTL);
Blockly.DropDownDiv.setColour(this.sourceBlock_.parentBlock_.getColour(), this.sourceBlock_.getColourTertiary());
Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_);
menu.setAllowAutoFocus(true);
menuDom.focus();
};

View file

@ -231,19 +231,6 @@ Blockly.FieldIconMenu.prototype.showEditor_ = function() {
contentDiv.appendChild(button);
}
contentDiv.style.width = Blockly.FieldIconMenu.DROPDOWN_WIDTH + 'px';
// Calculate positioning for the drop-down
// sourceBlock_ is the rendered shadow field button
var scale = this.sourceBlock_.workspace.scale;
var bBox = this.sourceBlock_.getHeightWidth();
bBox.width *= scale;
bBox.height *= scale;
var position = this.getAbsoluteXY_();
// If we can fit it, render below the shadow block
var primaryX = position.x + bBox.width / 2;
var primaryY = position.y + bBox.height;
// If we can't fit it, render above the entire parent block
var secondaryX = primaryX;
var secondaryY = position.y - (Blockly.BlockSvg.MIN_BLOCK_Y * scale) - (Blockly.BlockSvg.FIELD_Y_OFFSET * scale);
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
@ -252,9 +239,13 @@ Blockly.FieldIconMenu.prototype.showEditor_ = function() {
this.sourceBlock_.setColour(this.sourceBlock_.getColourSecondary(),
this.sourceBlock_.getColourSecondary(), this.sourceBlock_.getColourTertiary());
Blockly.DropDownDiv.setBoundsElement(this.sourceBlock_.workspace.getParentSvg().parentNode);
var renderedPrimary = Blockly.DropDownDiv.show(this, primaryX, primaryY,
secondaryX, secondaryY, this.onHide_.bind(this));
var scale = this.sourceBlock_.workspace.scale;
// Offset for icon-type horizontal blocks.
var secondaryYOffset = (
-(Blockly.BlockSvg.MIN_BLOCK_Y * scale) - (Blockly.BlockSvg.FIELD_Y_OFFSET * scale)
);
var renderedPrimary = Blockly.DropDownDiv.showPositionedByBlock(
this, this.sourceBlock_, this.onHide_.bind(this), secondaryYOffset);
if (!renderedPrimary) {
// Adjust for rotation
var arrowX = this.arrowX_ + Blockly.DropDownDiv.ARROW_SIZE / 1.5 + 1;