mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-28 22:10:31 -04:00
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:
parent
7c35d4e6f5
commit
25e80c2d09
4 changed files with 108 additions and 63 deletions
72
core/css.js
72
core/css.js
|
@ -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;',
|
||||
|
|
|
@ -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.,
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue