mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-28 22:10:31 -04:00
Horizontal toolbox layout with positioning at start or end.
This commit is contained in:
parent
cea4c0a733
commit
18a1550285
10 changed files with 1045 additions and 191 deletions
|
@ -445,7 +445,7 @@ Blockly.getMainWorkspaceMetrics_ = function() {
|
|||
var bottomEdge = topEdge + blockBox.height;
|
||||
}
|
||||
var absoluteLeft = 0;
|
||||
if (!this.RTL && this.toolbox_) {
|
||||
if (this.toolbox_ && this.toolbox_.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
||||
absoluteLeft = this.toolbox_.width;
|
||||
}
|
||||
var metrics = {
|
||||
|
|
|
@ -162,3 +162,28 @@ Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE] = Blockly.OUTPUT_VALUE;
|
|||
Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE] = Blockly.INPUT_VALUE;
|
||||
Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT] = Blockly.PREVIOUS_STATEMENT;
|
||||
Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT] = Blockly.NEXT_STATEMENT;
|
||||
|
||||
|
||||
/**
|
||||
* ENUM for toolbox and flyout at top of screen.
|
||||
* @const
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_TOP = 0;
|
||||
|
||||
/**
|
||||
* ENUM for toolbox and flyout at bottom of screen.
|
||||
* @const
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_BOTTOM = 1;
|
||||
|
||||
/**
|
||||
* ENUM for toolbox and flyout at left of screen.
|
||||
* @const
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_LEFT = 2;
|
||||
|
||||
/**
|
||||
* ENUM for toolbox and flyout at right of screen.
|
||||
* @const
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_RIGHT = 3;
|
||||
|
|
18
core/css.js
18
core/css.js
|
@ -430,6 +430,16 @@ Blockly.Css.CONTENT = [
|
|||
'white-space: nowrap;',
|
||||
'}',
|
||||
|
||||
'.blocklyHorizontalTree {',
|
||||
'float: left;',
|
||||
'margin: 1px 5px 8px 0px;',
|
||||
'}',
|
||||
|
||||
'.blocklyHorizontalTreeRtl {',
|
||||
'float: right;',
|
||||
'margin: 1px 0px 8px 5px;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',
|
||||
'margin-left: 8px;',
|
||||
'}',
|
||||
|
@ -444,6 +454,14 @@ Blockly.Css.CONTENT = [
|
|||
'margin: 5px 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSeparatorHorizontal {',
|
||||
'border-right: solid #e5e5e5 1px;',
|
||||
'width: 0px;',
|
||||
'padding: 5px 0;',
|
||||
'margin: 0 5px;',
|
||||
'}',
|
||||
|
||||
|
||||
'.blocklyTreeIcon {',
|
||||
'background-image: url(<<<PATH>>>/sprites.png);',
|
||||
'height: 16px;',
|
||||
|
|
497
core/flyout.js
497
core/flyout.js
|
@ -56,6 +56,18 @@ Blockly.Flyout = function(workspaceOptions) {
|
|||
*/
|
||||
this.RTL = !!workspaceOptions.RTL;
|
||||
|
||||
/**
|
||||
* Flyout should be laid out horizontally vs vertically.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.horizontalLayout_ = workspaceOptions.horizontalLayout;
|
||||
|
||||
/**
|
||||
* Position of the toolbox and flyout relative to the workspace.
|
||||
* @type {number}
|
||||
*/
|
||||
this.toolboxPosition_ = workspaceOptions.toolboxPosition;
|
||||
|
||||
/**
|
||||
* Opaque data that can be passed to Blockly.unbindEvent_.
|
||||
* @type {!Array.<!Array>}
|
||||
|
@ -120,6 +132,13 @@ Blockly.Flyout.prototype.width_ = 0;
|
|||
*/
|
||||
Blockly.Flyout.prototype.height_ = 0;
|
||||
|
||||
/**
|
||||
* Vertical offset of flyout.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.verticalOffset_ = 0;
|
||||
|
||||
/**
|
||||
* Creates the flyout's DOM. Only needs to be called once.
|
||||
* @return {!Element} The flyout's SVG group.
|
||||
|
@ -148,7 +167,8 @@ Blockly.Flyout.prototype.init = function(targetWorkspace) {
|
|||
this.targetWorkspace_ = targetWorkspace;
|
||||
this.workspace_.targetWorkspace = targetWorkspace;
|
||||
// Add scrollbar.
|
||||
this.scrollbar_ = new Blockly.Scrollbar(this.workspace_, false, false);
|
||||
this.scrollbar_ = new Blockly.Scrollbar(this.workspace_,
|
||||
this.horizontalLayout_, false);
|
||||
|
||||
this.hide();
|
||||
|
||||
|
@ -197,9 +217,12 @@ Blockly.Flyout.prototype.dispose = function() {
|
|||
* .viewHeight: Height of the visible rectangle,
|
||||
* .viewWidth: Width of the visible rectangle,
|
||||
* .contentHeight: Height of the contents,
|
||||
* .contentWidth: Width of the contents,
|
||||
* .viewTop: Offset of top edge of visible rectangle from parent,
|
||||
* .contentTop: Offset of the top-most content from the y=0 coordinate,
|
||||
* .absoluteTop: Top-edge of view.
|
||||
* .viewLeft: Offset of the left edge of visible rectangle from parent,
|
||||
* .contentLeft: Offset of the left-most content from the x=0 coordinate,
|
||||
* .absoluteLeft: Left-edge of view.
|
||||
* @return {Object} Contains size and position metrics of the flyout.
|
||||
* @private
|
||||
|
@ -209,44 +232,77 @@ Blockly.Flyout.prototype.getMetrics_ = function() {
|
|||
// Flyout is hidden.
|
||||
return null;
|
||||
}
|
||||
var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING;
|
||||
var viewWidth = this.width_;
|
||||
|
||||
try {
|
||||
var optionBox = this.workspace_.getCanvas().getBBox();
|
||||
} catch (e) {
|
||||
// Firefox has trouble with hidden elements (Bug 528969).
|
||||
var optionBox = {height: 0, y: 0};
|
||||
}
|
||||
return {
|
||||
|
||||
var absoluteTop = this.verticalOffset_ + this.SCROLLBAR_PADDING
|
||||
if (this.horizontalLayout_) {
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
absoluteTop = 0;
|
||||
}
|
||||
var viewHeight = this.height_;
|
||||
var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING;
|
||||
} else {
|
||||
var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING;
|
||||
var viewWidth = this.width_;
|
||||
}
|
||||
|
||||
var metrics = {
|
||||
viewHeight: viewHeight,
|
||||
viewWidth: viewWidth,
|
||||
contentHeight: (optionBox.height + optionBox.y) * this.workspace_.scale,
|
||||
contentHeight: (optionBox.height) * this.workspace_.scale,
|
||||
contentWidth: (optionBox.width) * this.workspace_.scale,
|
||||
viewTop: -this.workspace_.scrollY,
|
||||
contentTop: 0,
|
||||
absoluteTop: this.SCROLLBAR_PADDING,
|
||||
absoluteLeft: 0
|
||||
viewLeft: -this.workspace_.scrollX,
|
||||
contentTop: optionBox.y,
|
||||
contentLeft: 0,
|
||||
absoluteTop: absoluteTop,
|
||||
absoluteLeft: this.SCROLLBAR_PADDING
|
||||
};
|
||||
return metrics;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the Y translation of the flyout to match the scrollbars.
|
||||
* @param {!Object} yRatio Contains a y property which is a float
|
||||
* between 0 and 1 specifying the degree of scrolling.
|
||||
* Sets the translation of the flyout to match the scrollbars.
|
||||
* @param {!Object} xyRatio Contains a y property which is a float
|
||||
* between 0 and 1 specifying the degree of scrolling and a
|
||||
* similar x property.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.setMetrics_ = function(yRatio) {
|
||||
Blockly.Flyout.prototype.setMetrics_ = function(xyRatio) {
|
||||
var metrics = this.getMetrics_();
|
||||
// This is a fix to an apparent race condition.
|
||||
if (!metrics) {
|
||||
return;
|
||||
}
|
||||
if (goog.isNumber(yRatio.y)) {
|
||||
if (!this.horizontalLayout_ && goog.isNumber(xyRatio.y)) {
|
||||
this.workspace_.scrollY =
|
||||
-metrics.contentHeight * yRatio.y - metrics.contentTop;
|
||||
-metrics.contentHeight * xyRatio.y - metrics.contentTop;
|
||||
} else if (this.horizontalLayout_ && goog.isNumber(xyRatio.x)) {
|
||||
if (this.RTL) {
|
||||
this.workspace_.scrollX =
|
||||
-metrics.contentWidth * xyRatio.x + metrics.contentLeft;
|
||||
} else {
|
||||
this.workspace_.scrollX =
|
||||
-metrics.contentWidth * xyRatio.x - metrics.contentLeft;
|
||||
}
|
||||
}
|
||||
this.workspace_.translate(0, this.workspace_.scrollY + metrics.absoluteTop);
|
||||
var translateX = this.horizontalLayout_ && this.RTL ?
|
||||
metrics.absoluteLeft + metrics.viewWidth - this.workspace_.scrollX :
|
||||
this.workspace_.scrollX + metrics.absoluteLeft;
|
||||
this.workspace_.translate(translateX,
|
||||
this.workspace_.scrollY + metrics.absoluteTop);
|
||||
};
|
||||
|
||||
Blockly.Flyout.prototype.setVerticalOffset = function(verticalOffset) {
|
||||
this.verticalOffset_ = verticalOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the toolbox to the edge of the workspace.
|
||||
*/
|
||||
|
@ -259,47 +315,144 @@ Blockly.Flyout.prototype.position = function() {
|
|||
// Hidden components will return null.
|
||||
return;
|
||||
}
|
||||
var edgeWidth = this.width_ - this.CORNER_RADIUS;
|
||||
if (this.RTL) {
|
||||
var edgeWidth = this.horizontalLayout_ ? metrics.viewWidth : this.width_;
|
||||
edgeWidth -= this.CORNER_RADIUS;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
edgeWidth *= -1;
|
||||
}
|
||||
var path = ['M ' + (this.RTL ? this.width_ : 0) + ',0'];
|
||||
path.push('h', edgeWidth);
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0,
|
||||
this.RTL ? 0 : 1,
|
||||
this.RTL ? -this.CORNER_RADIUS : this.CORNER_RADIUS,
|
||||
this.CORNER_RADIUS);
|
||||
path.push('v', Math.max(0, metrics.viewHeight - this.CORNER_RADIUS * 2));
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0,
|
||||
this.RTL ? 0 : 1,
|
||||
this.RTL ? this.CORNER_RADIUS : -this.CORNER_RADIUS,
|
||||
this.CORNER_RADIUS);
|
||||
path.push('h', -edgeWidth);
|
||||
path.push('z');
|
||||
this.svgBackground_.setAttribute('d', path.join(' '));
|
||||
|
||||
this.setBackgroundPath_(edgeWidth,
|
||||
this.horizontalLayout_ ? this.height_ + this.verticalOffset_ : metrics.viewHeight);
|
||||
|
||||
var x = metrics.absoluteLeft;
|
||||
if (this.RTL) {
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
x += metrics.viewWidth;
|
||||
x -= this.width_;
|
||||
}
|
||||
this.svgGroup_.setAttribute('transform',
|
||||
'translate(' + x + ',' + metrics.absoluteTop + ')');
|
||||
|
||||
// Record the height for Blockly.Flyout.getMetrics_.
|
||||
this.height_ = metrics.viewHeight;
|
||||
var y = metrics.absoluteTop;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
y += metrics.viewHeight;
|
||||
y -= this.height_;
|
||||
}
|
||||
|
||||
this.svgGroup_.setAttribute('transform',
|
||||
'translate(' + x + ',' + y + ')');
|
||||
|
||||
// Record the height for Blockly.Flyout.getMetrics_, or width if the layout is
|
||||
// horizontal.
|
||||
if (this.horizontalLayout_) {
|
||||
this.width_ = metrics.viewWidth;
|
||||
} else {
|
||||
this.height_ = metrics.viewHeight;
|
||||
}
|
||||
|
||||
// Update the scrollbar (if one exists).
|
||||
if (this.scrollbar_) {
|
||||
this.scrollbar_.resize();
|
||||
}
|
||||
// The blocks need to be visible in order to be laid out and measured
|
||||
// correctly, but we don't want the flyout to show up until it's properly
|
||||
// sized. Opacity is set to zero in show().
|
||||
this.svgGroup_.style.opacity = 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and set the path for the visible boundaries of the toolbox.
|
||||
* @param {number} width The width of the toolbox, not including the
|
||||
* rounded corners.
|
||||
* @param {number} height The height of the toolbox, not including
|
||||
* rounded corners.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.setBackgroundPath_ = function(width, height) {
|
||||
if (this.horizontalLayout_) {
|
||||
this.setBackgroundPathHorizontal_(width, height);
|
||||
} else {
|
||||
this.setBackgroundPathVertical_(width, height);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and set the path for the visible boundaries of the toolbox in vertical mode.
|
||||
* @param {number} width The width of the toolbox, not including the
|
||||
* rounded corners.
|
||||
* @param {number} height The height of the toolbox, not including
|
||||
* rounded corners.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.setBackgroundPathVertical_ = function(width, height) {
|
||||
var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT;
|
||||
// Decide whether to start on the left or right.
|
||||
var path = ['M ' + (atRight ? this.width_ : 0) + ',0'];
|
||||
// Top.
|
||||
path.push('h', width);
|
||||
// Rounded corner.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0,
|
||||
atRight ? 0 : 1,
|
||||
atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS,
|
||||
this.CORNER_RADIUS);
|
||||
// Side closest to workspace.
|
||||
path.push('v', Math.max(0, height - this.CORNER_RADIUS * 2));
|
||||
// Rounded corner.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0,
|
||||
atRight ? 0 : 1,
|
||||
atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS,
|
||||
this.CORNER_RADIUS);
|
||||
// Bottom.
|
||||
path.push('h', -width);
|
||||
path.push('z');
|
||||
this.svgBackground_.setAttribute('d', path.join(' '));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and set the path for the visible boundaries of the toolbox in horizontal mode.
|
||||
* @param {number} width The width of the toolbox, not including the
|
||||
* rounded corners.
|
||||
* @param {number} height The height of the toolbox, not including
|
||||
* rounded corners.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = function(width, height) {
|
||||
var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP;
|
||||
// start at top left.
|
||||
var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)];
|
||||
|
||||
if (atTop) {
|
||||
// top
|
||||
path.push('h', width + this.CORNER_RADIUS);
|
||||
// right
|
||||
path.push('v', height);
|
||||
// bottom
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
-this.CORNER_RADIUS, this.CORNER_RADIUS);
|
||||
path.push('h', -1 * (width - this.CORNER_RADIUS));
|
||||
// left
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
-this.CORNER_RADIUS, -this.CORNER_RADIUS);
|
||||
path.push('z');
|
||||
} else {
|
||||
// top
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
this.CORNER_RADIUS, -this.CORNER_RADIUS);
|
||||
path.push('h', width - this.CORNER_RADIUS);
|
||||
// right
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
this.CORNER_RADIUS, this.CORNER_RADIUS);
|
||||
path.push('v', height - this.CORNER_RADIUS);
|
||||
// bottom
|
||||
path.push('h', -width - this.CORNER_RADIUS);
|
||||
// left
|
||||
path.push('z');
|
||||
}
|
||||
this.svgBackground_.setAttribute('d', path.join(' '));
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll the flyout to the top.
|
||||
*/
|
||||
Blockly.Flyout.prototype.scrollToStart = function() {
|
||||
this.scrollbar_.set(0);
|
||||
this.scrollbar_.set((this.horizontalLayout_ && this.RTL) ? 1000000000 : 0);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -308,6 +461,10 @@ Blockly.Flyout.prototype.scrollToStart = function() {
|
|||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.wheel_ = function(e) {
|
||||
// Don't scroll sideways.
|
||||
if (this.horizontalLayout_) {
|
||||
return;
|
||||
}
|
||||
var delta = e.deltaY;
|
||||
if (delta) {
|
||||
if (goog.userAgent.GECKO) {
|
||||
|
@ -405,7 +562,14 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
|||
}
|
||||
}
|
||||
|
||||
// Lay out the blocks vertically.
|
||||
// The blocks need to be visible in order to be laid out and measured
|
||||
// correctly, but we don't want the flyout to show up until it's properly
|
||||
// sized. Opacity is reset at the end of position().
|
||||
this.svgGroup_.style.opacity = 0;
|
||||
this.svgGroup_.style.display = 'block';
|
||||
|
||||
// Lay out the blocks.
|
||||
var cursorX = margin / this.workspace_.scale + Blockly.BlockSvg.TAB_WIDTH;
|
||||
var cursorY = margin;
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
var allBlocks = block.getDescendants();
|
||||
|
@ -418,10 +582,12 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
|||
block.render();
|
||||
var root = block.getSvgRoot();
|
||||
var blockHW = block.getHeightWidth();
|
||||
var x = this.RTL ? 0 : margin / this.workspace_.scale +
|
||||
Blockly.BlockSvg.TAB_WIDTH;
|
||||
block.moveBy(x, cursorY);
|
||||
cursorY += blockHW.height + gaps[i];
|
||||
block.moveBy((this.horizontalLayout_ && this.RTL) ? -cursorX : cursorX, cursorY);
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += blockHW.width + gaps[i];
|
||||
} else {
|
||||
cursorY += blockHW.height + gaps[i];
|
||||
}
|
||||
|
||||
// Create an invisible rectangle under the block to act as a button. Just
|
||||
// using the block as a button is poor, since blocks have holes in them.
|
||||
|
@ -431,6 +597,44 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
|||
block.flyoutRect_ = rect;
|
||||
this.buttons_[i] = rect;
|
||||
|
||||
this.addBlockListeners_(root, block, rect);
|
||||
}
|
||||
|
||||
// IE 11 is an incompetant browser that fails to fire mouseout events.
|
||||
// When the mouse is over the background, deselect all blocks.
|
||||
var deselectAll = function(e) {
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
block.removeSelect();
|
||||
}
|
||||
};
|
||||
this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover',
|
||||
this, deselectAll));
|
||||
|
||||
if (this.horizontalLayout_) {
|
||||
this.height_ = 0;
|
||||
} else {
|
||||
this.width_ = 0;
|
||||
}
|
||||
this.reflow();
|
||||
|
||||
this.filterForCapacity_();
|
||||
|
||||
// Fire a resize event to update the flyout's scrollbar.
|
||||
Blockly.fireUiEventNow(window, 'resize');
|
||||
this.reflowWrapper_ = this.reflow.bind(this);
|
||||
this.workspace_.addChangeListener(this.reflowWrapper_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add listeners to a block that has been added to the flyout.
|
||||
* @param {Element} root The root node of the SVG group the block is in.
|
||||
* @param {!Blockly.Block} block The block to add listeners for.
|
||||
* @param {!Element} rect The invisible rectangle under the block that acts as
|
||||
* a button for that block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) {
|
||||
if (this.autoClose) {
|
||||
this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null,
|
||||
this.createBlockFunc_(block)));
|
||||
|
@ -448,76 +652,6 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
|||
block.addSelect));
|
||||
this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block,
|
||||
block.removeSelect));
|
||||
}
|
||||
|
||||
// IE 11 is an incompetant browser that fails to fire mouseout events.
|
||||
// When the mouse is over the background, deselect all blocks.
|
||||
var deselectAll = function(e) {
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
block.removeSelect();
|
||||
}
|
||||
};
|
||||
this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover',
|
||||
this, deselectAll));
|
||||
|
||||
this.width_ = 0;
|
||||
this.reflow();
|
||||
|
||||
this.filterForCapacity_();
|
||||
|
||||
// Fire a resize event to update the flyout's scrollbar.
|
||||
Blockly.fireUiEventNow(window, 'resize');
|
||||
this.reflowWrapper_ = this.reflow.bind(this);
|
||||
this.workspace_.addChangeListener(this.reflowWrapper_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute width of flyout. Position button under each block.
|
||||
* For RTL: Lay out the blocks right-aligned.
|
||||
*/
|
||||
Blockly.Flyout.prototype.reflow = function() {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var flyoutWidth = 0;
|
||||
var margin = this.CORNER_RADIUS;
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
for (var x = 0, block; block = blocks[x]; x++) {
|
||||
var width = block.getHeightWidth().width;
|
||||
if (block.outputConnection) {
|
||||
width -= Blockly.BlockSvg.TAB_WIDTH;
|
||||
}
|
||||
flyoutWidth = Math.max(flyoutWidth, width);
|
||||
}
|
||||
flyoutWidth += Blockly.BlockSvg.TAB_WIDTH;
|
||||
flyoutWidth *= this.workspace_.scale;
|
||||
flyoutWidth += margin * 1.5 + Blockly.Scrollbar.scrollbarThickness;
|
||||
if (this.width_ != flyoutWidth) {
|
||||
for (var x = 0, block; block = blocks[x]; x++) {
|
||||
var blockHW = block.getHeightWidth();
|
||||
if (this.RTL) {
|
||||
// With the flyoutWidth known, right-align the blocks.
|
||||
var oldX = block.getRelativeToSurfaceXY().x;
|
||||
var dx = flyoutWidth - margin;
|
||||
dx /= this.workspace_.scale;
|
||||
dx -= Blockly.BlockSvg.TAB_WIDTH;
|
||||
block.moveBy(dx - oldX, 0);
|
||||
}
|
||||
if (block.flyoutRect_) {
|
||||
block.flyoutRect_.setAttribute('width', blockHW.width);
|
||||
block.flyoutRect_.setAttribute('height', blockHW.height);
|
||||
// Blocks with output tabs are shifted a bit.
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
var blockXY = block.getRelativeToSurfaceXY();
|
||||
block.flyoutRect_.setAttribute('x',
|
||||
this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab);
|
||||
block.flyoutRect_.setAttribute('y', blockXY.y);
|
||||
}
|
||||
}
|
||||
// Record the width for .getMetrics_ and .position.
|
||||
this.width_ = flyoutWidth;
|
||||
// Fire a resize event to update the flyout's scrollbar.
|
||||
Blockly.fireUiEvent(window, 'resize');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -578,13 +712,23 @@ Blockly.Flyout.prototype.onMouseDown_ = function(e) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.onMouseMove_ = function(e) {
|
||||
var dy = e.clientY - this.startDragMouseY_;
|
||||
this.startDragMouseY_ = e.clientY;
|
||||
var metrics = this.getMetrics_();
|
||||
var y = metrics.viewTop - dy;
|
||||
y = Math.min(y, metrics.contentHeight - metrics.viewHeight);
|
||||
y = Math.max(y, 0);
|
||||
this.scrollbar_.set(y);
|
||||
if (this.horizontalLayout_) {
|
||||
var dx = e.clientX - this.startDragMouseX_;
|
||||
this.startDragMouseX_ = e.clientX;
|
||||
var metrics = this.getMetrics_();
|
||||
var x = metrics.viewLeft - dx;
|
||||
x = Math.min(x, metrics.contentWidth - metrics.viewWidth);
|
||||
x = Math.max(x, 0);
|
||||
this.scrollbar_.set(x);
|
||||
} else {
|
||||
var dy = e.clientY - this.startDragMouseY_;
|
||||
this.startDragMouseY_ = e.clientY;
|
||||
var metrics = this.getMetrics_();
|
||||
var y = metrics.viewTop - dy;
|
||||
y = Math.min(y, metrics.contentHeight - metrics.viewHeight);
|
||||
y = Math.max(y, 0);
|
||||
this.scrollbar_.set(y);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -644,7 +788,7 @@ Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) {
|
|||
}
|
||||
var xyOld = Blockly.getSvgXY_(svgRootOld, workspace);
|
||||
// Scale the scroll (getSvgXY_ did not do this).
|
||||
if (flyout.RTL) {
|
||||
if (flyout.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
var width = workspace.getMetrics().viewWidth - flyout.width_;
|
||||
xyOld.x += width / workspace.scale - width;
|
||||
} else {
|
||||
|
@ -708,13 +852,24 @@ Blockly.Flyout.prototype.getClientRect = function() {
|
|||
// area are still deleted. Must be larger than the largest screen size,
|
||||
// but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE).
|
||||
var BIG_NUM = 1000000000;
|
||||
if (this.RTL) {
|
||||
var width = flyoutRect.left + flyoutRect.width + BIG_NUM;
|
||||
return new goog.math.Rect(flyoutRect.left, -BIG_NUM, width, BIG_NUM * 2);
|
||||
var x = flyoutRect.left;
|
||||
var y = flyoutRect.top;
|
||||
var width = flyoutRect.width;
|
||||
var height = flyoutRect.height;
|
||||
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
|
||||
return new goog.math.Rect(-BIG_NUM, y - BIG_NUM, BIG_NUM * 2,
|
||||
BIG_NUM + height);
|
||||
} else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
return new goog.math.Rect(-BIG_NUM, y + this.verticalOffset_, BIG_NUM * 2,
|
||||
BIG_NUM + height);
|
||||
} else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
|
||||
return new goog.math.Rect(x - BIG_NUM, -BIG_NUM, BIG_NUM + width,
|
||||
BIG_NUM * 2);
|
||||
} else { // Right
|
||||
return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width,
|
||||
BIG_NUM * 2);
|
||||
}
|
||||
// LTR
|
||||
var width = BIG_NUM + flyoutRect.width + flyoutRect.left;
|
||||
return new goog.math.Rect(-BIG_NUM, -BIG_NUM, width, BIG_NUM * 2);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -742,3 +897,93 @@ Blockly.Flyout.terminateDrag_ = function() {
|
|||
Blockly.Flyout.startBlock_ = null;
|
||||
Blockly.Flyout.startFlyout_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute height of flyout. Position button under each block.
|
||||
* For RTL: Lay out the blocks right-aligned.
|
||||
*/
|
||||
Blockly.Flyout.prototype.reflowHorizontal = function() {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var flyoutHeight = 0;
|
||||
var margin = this.CORNER_RADIUS;
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
for (var x = 0, block; block = blocks[x]; x++) {
|
||||
var height = block.getHeightWidth().height;
|
||||
flyoutHeight = Math.max(flyoutHeight, height);
|
||||
}
|
||||
flyoutHeight *= this.workspace_.scale;
|
||||
flyoutHeight += margin * 1.5 + Blockly.Scrollbar.scrollbarThickness;
|
||||
if (this.height_ != flyoutHeight) {
|
||||
for (var x = 0, block; block = blocks[x]; x++) {
|
||||
var blockHW = block.getHeightWidth();
|
||||
if (block.flyoutRect_) {
|
||||
block.flyoutRect_.setAttribute('width', blockHW.width);
|
||||
block.flyoutRect_.setAttribute('height', blockHW.height);
|
||||
// Blocks with output tabs are shifted a bit.
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
var blockXY = block.getRelativeToSurfaceXY();
|
||||
block.flyoutRect_.setAttribute('y', blockXY.y);
|
||||
block.flyoutRect_.setAttribute('x',
|
||||
this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab);
|
||||
}
|
||||
}
|
||||
// Record the width for .getMetrics_ and .position.
|
||||
this.height_ = flyoutHeight;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute width of flyout. Position button under each block.
|
||||
* For RTL: Lay out the blocks right-aligned.
|
||||
*/
|
||||
Blockly.Flyout.prototype.reflowVertical = function() {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var flyoutWidth = 0;
|
||||
var margin = this.CORNER_RADIUS;
|
||||
var blocks = this.workspace_.getTopBlocks(false);
|
||||
for (var x = 0, block; block = blocks[x]; x++) {
|
||||
var width = block.getHeightWidth().width;
|
||||
if (block.outputConnection) {
|
||||
width -= Blockly.BlockSvg.TAB_WIDTH;
|
||||
}
|
||||
flyoutWidth = Math.max(flyoutWidth, width);
|
||||
}
|
||||
flyoutWidth += Blockly.BlockSvg.TAB_WIDTH;
|
||||
flyoutWidth *= this.workspace_.scale;
|
||||
flyoutWidth += margin * 1.5 + Blockly.Scrollbar.scrollbarThickness;
|
||||
if (this.width_ != flyoutWidth) {
|
||||
for (var x = 0, block; block = blocks[x]; x++) {
|
||||
var blockHW = block.getHeightWidth();
|
||||
if (this.RTL) {
|
||||
// With the flyoutWidth known, right-align the blocks.
|
||||
var oldX = block.getRelativeToSurfaceXY().x;
|
||||
var dx = flyoutWidth - margin;
|
||||
dx /= this.workspace_.scale;
|
||||
dx -= Blockly.BlockSvg.TAB_WIDTH;
|
||||
block.moveBy(dx - oldX, 0);
|
||||
}
|
||||
if (block.flyoutRect_) {
|
||||
block.flyoutRect_.setAttribute('width', blockHW.width);
|
||||
block.flyoutRect_.setAttribute('height', blockHW.height);
|
||||
// Blocks with output tabs are shifted a bit.
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
var blockXY = block.getRelativeToSurfaceXY();
|
||||
block.flyoutRect_.setAttribute('x',
|
||||
this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab);
|
||||
block.flyoutRect_.setAttribute('y', blockXY.y);
|
||||
}
|
||||
}
|
||||
// Record the width for .getMetrics_ and .position.
|
||||
this.width_ = flyoutWidth;
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Flyout.prototype.reflow = function() {
|
||||
if (this.horizontalLayout_) {
|
||||
this.reflowHorizontal();
|
||||
} else {
|
||||
this.reflowVertical();
|
||||
}
|
||||
// Fire a resize event to update the flyout's scrollbar.
|
||||
Blockly.fireUiEvent(window, 'resize');
|
||||
};
|
|
@ -276,7 +276,7 @@ Blockly.init_ = function(mainWorkspace) {
|
|||
mainWorkspace.flyout_.show(options.languageTree.childNodes);
|
||||
// Translate the workspace sideways to avoid the fixed flyout.
|
||||
mainWorkspace.scrollX = mainWorkspace.flyout_.width_;
|
||||
if (options.RTL) {
|
||||
if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
mainWorkspace.scrollX *= -1;
|
||||
}
|
||||
mainWorkspace.translate(mainWorkspace.scrollX, 0);
|
||||
|
|
|
@ -69,6 +69,26 @@ Blockly.Options = function(options) {
|
|||
hasSounds = true;
|
||||
}
|
||||
}
|
||||
var rtl = !!options['rtl'];
|
||||
var horizontalLayout = options['horizontalLayout'];
|
||||
if (horizontalLayout === undefined) {
|
||||
horizontalLayout = false;
|
||||
}
|
||||
var toolboxAtStart = options['toolboxPosition'];
|
||||
if (toolboxAtStart === 'end') {
|
||||
toolboxAtStart = false;
|
||||
} else {
|
||||
toolboxAtStart = true;
|
||||
}
|
||||
|
||||
if (horizontalLayout) {
|
||||
var toolboxPosition = toolboxAtStart ?
|
||||
Blockly.TOOLBOX_AT_TOP : Blockly.TOOLBOX_AT_BOTTOM;
|
||||
} else {
|
||||
var toolboxPosition = (toolboxAtStart == rtl) ?
|
||||
Blockly.TOOLBOX_AT_RIGHT : Blockly.TOOLBOX_AT_LEFT;
|
||||
}
|
||||
|
||||
var hasScrollbars = options['scrollbars'];
|
||||
if (hasScrollbars === undefined) {
|
||||
hasScrollbars = hasCategories;
|
||||
|
@ -85,21 +105,28 @@ Blockly.Options = function(options) {
|
|||
pathToMedia = options['path'] + 'media/';
|
||||
}
|
||||
|
||||
this.RTL = !!options['rtl'];
|
||||
var enableRealtime = !!options['realtime'];
|
||||
var realtimeOptions = enableRealtime ? options['realtimeOptions'] : undefined;
|
||||
|
||||
this.RTL = rtl;
|
||||
this.collapse = hasCollapse;
|
||||
this.comments = hasComments;
|
||||
this.disable = hasDisable;
|
||||
this.readOnly = readOnly;
|
||||
this.maxBlocks = options['maxBlocks'] || Infinity;
|
||||
this.maxBlocks = options['maxBlocks'] || Infinity;
|
||||
this.pathToMedia = pathToMedia;
|
||||
this.hasCategories = hasCategories;
|
||||
this.hasScrollbars = hasScrollbars;
|
||||
this.hasTrashcan = hasTrashcan;
|
||||
this.hasSounds = hasSounds;
|
||||
this.hasCss = hasCss;
|
||||
this.horizontalLayout = horizontalLayout;
|
||||
this.languageTree = languageTree;
|
||||
this.gridOptions = Blockly.Options.parseGridOptions_(options);
|
||||
this.zoomOptions = Blockly.Options.parseZoomOptions_(options);
|
||||
this.enableRealtime = enableRealtime;
|
||||
this.realtimeOptions = realtimeOptions;
|
||||
this.toolboxPosition = toolboxPosition;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
193
core/toolbox.js
193
core/toolbox.js
|
@ -31,6 +31,7 @@ goog.require('goog.dom');
|
|||
goog.require('goog.events');
|
||||
goog.require('goog.events.BrowserFeature');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.html.SafeStyle');
|
||||
goog.require('goog.math.Rect');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.tree.TreeControl');
|
||||
|
@ -50,14 +51,78 @@ Blockly.Toolbox = function(workspace) {
|
|||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Is RTL vs LTR.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.RTL = workspace.options.RTL;
|
||||
|
||||
/**
|
||||
* Whether the toolbox should be laid out horizontally.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.horizontalLayout_ = workspace.options.horizontalLayout;
|
||||
|
||||
/**
|
||||
* Position of the toolbox and flyout relative to the workspace.
|
||||
* @type {number}
|
||||
*/
|
||||
this.toolboxPosition = workspace.options.toolboxPosition;
|
||||
|
||||
/**
|
||||
* Configuration constants for Closure's tree UI.
|
||||
* @type {Object.<string,*>}
|
||||
* @private
|
||||
*/
|
||||
this.config_ = {
|
||||
indentWidth: 19,
|
||||
cssRoot: 'blocklyTreeRoot',
|
||||
cssHideRoot: 'blocklyHidden',
|
||||
cssItem: '',
|
||||
cssTreeRow: 'blocklyTreeRow',
|
||||
cssItemLabel: 'blocklyTreeLabel',
|
||||
cssTreeIcon: 'blocklyTreeIcon',
|
||||
cssExpandedFolderIcon: 'blocklyTreeIconOpen',
|
||||
cssFileIcon: 'blocklyTreeIconNone',
|
||||
cssSelectedRow: 'blocklyTreeSelected'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Configuration constants for tree separator.
|
||||
* @type {Object.<string,*>}
|
||||
* @private
|
||||
*/
|
||||
this.treeSeparatorConfig_ = {
|
||||
cssTreeRow: 'blocklyTreeSeparator'
|
||||
};
|
||||
|
||||
if (this.horizontalLayout_) {
|
||||
this.config_['cssTreeRow'] =
|
||||
this.config_['cssTreeRow'] +
|
||||
(workspace.RTL ? ' blocklyHorizontalTreeRtl' : ' blocklyHorizontalTree');
|
||||
|
||||
this.treeSeparatorConfig_['cssTreeRow'] =
|
||||
'blocklyTreeSeparatorHorizontal' +
|
||||
(workspace.RTL ? ' blocklyHorizontalTreeRtl' : ' blocklyHorizontalTree');
|
||||
this.config_['cssTreeIcon'] = '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Width of the toolbox.
|
||||
* Width of the toolbox, which changes only in vertical layout.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.Toolbox.prototype.width = 0;
|
||||
|
||||
/**
|
||||
* Height of the toolbox, which changes only in horizontal layout.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.Toolbox.prototype.height = 0;
|
||||
|
||||
/**
|
||||
* The SVG group currently selected.
|
||||
* @type {SVGGElement}
|
||||
|
@ -72,25 +137,6 @@ Blockly.Toolbox.prototype.selectedOption_ = null;
|
|||
*/
|
||||
Blockly.Toolbox.prototype.lastCategory_ = null;
|
||||
|
||||
/**
|
||||
* Configuration constants for Closure's tree UI.
|
||||
* @type {Object.<string,*>}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.CONFIG_ = {
|
||||
indentWidth: 19,
|
||||
cssRoot: 'blocklyTreeRoot',
|
||||
cssHideRoot: 'blocklyHidden',
|
||||
cssItem: '',
|
||||
cssTreeRow: 'blocklyTreeRow',
|
||||
cssItemLabel: 'blocklyTreeLabel',
|
||||
cssTreeIcon: 'blocklyTreeIcon',
|
||||
cssExpandedFolderIcon: 'blocklyTreeIconOpen',
|
||||
cssFileIcon: 'blocklyTreeIconNone',
|
||||
cssSelectedRow: 'blocklyTreeSelected'
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the toolbox.
|
||||
*/
|
||||
|
@ -116,7 +162,9 @@ Blockly.Toolbox.prototype.init = function() {
|
|||
var workspaceOptions = {
|
||||
disabledPatternId: workspace.options.disabledPatternId,
|
||||
parentWorkspace: workspace,
|
||||
RTL: workspace.RTL
|
||||
RTL: workspace.RTL,
|
||||
horizontalLayout: workspace.horizontalLayout,
|
||||
toolboxPosition: workspace.options.toolboxPosition
|
||||
};
|
||||
/**
|
||||
* @type {!Blockly.Flyout}
|
||||
|
@ -126,10 +174,10 @@ Blockly.Toolbox.prototype.init = function() {
|
|||
goog.dom.insertSiblingAfter(this.flyout_.createDom(), workspace.svgGroup_);
|
||||
this.flyout_.init(workspace);
|
||||
|
||||
this.CONFIG_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif';
|
||||
this.CONFIG_['cssCollapsedFolderIcon'] =
|
||||
this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif';
|
||||
this.config_['cssCollapsedFolderIcon'] =
|
||||
'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr');
|
||||
var tree = new Blockly.Toolbox.TreeControl(this, this.CONFIG_);
|
||||
var tree = new Blockly.Toolbox.TreeControl(this, this.config_);
|
||||
this.tree_ = tree;
|
||||
tree.setShowRootNode(false);
|
||||
tree.setShowLines(false);
|
||||
|
@ -164,18 +212,33 @@ Blockly.Toolbox.prototype.position = function() {
|
|||
var svg = this.workspace_.getParentSvg();
|
||||
var svgPosition = goog.style.getPageOffset(svg);
|
||||
var svgSize = Blockly.svgSize(svg);
|
||||
if (this.workspace_.RTL) {
|
||||
treeDiv.style.left =
|
||||
(svgPosition.x + svgSize.width - treeDiv.offsetWidth) + 'px';
|
||||
} else {
|
||||
if (this.horizontalLayout_) {
|
||||
treeDiv.style.left = svgPosition.x + 'px';
|
||||
}
|
||||
treeDiv.style.height = svgSize.height + 'px';
|
||||
treeDiv.style.top = svgPosition.y + 'px';
|
||||
this.width = treeDiv.offsetWidth;
|
||||
if (!this.workspace_.RTL) {
|
||||
// For some reason the LTR toolbox now reports as 1px too wide.
|
||||
this.width -= 1;
|
||||
treeDiv.style.height = 'auto';
|
||||
treeDiv.style.width = svgSize.width + 'px';
|
||||
this.height = treeDiv.offsetHeight;
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { // Top
|
||||
treeDiv.style.top = svgPosition.y + 'px';
|
||||
this.flyout_.setVerticalOffset(treeDiv.offsetHeight);
|
||||
} else { // Bottom
|
||||
var topOfToolbox = svgPosition.y + svgSize.height;
|
||||
treeDiv.style.top = topOfToolbox + 'px';
|
||||
this.flyout_.setVerticalOffset(topOfToolbox);
|
||||
}
|
||||
} else {
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { // Right
|
||||
treeDiv.style.left =
|
||||
(svgPosition.x + svgSize.width - treeDiv.offsetWidth) + 'px';
|
||||
} else { // Left
|
||||
treeDiv.style.left = svgPosition.x + 'px';
|
||||
}
|
||||
treeDiv.style.height = svgSize.height + 'px';
|
||||
treeDiv.style.top = svgPosition.y + 'px';
|
||||
this.width = treeDiv.offsetWidth;
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
||||
// For some reason the LTR toolbox now reports as 1px too wide.
|
||||
this.width -= 1;
|
||||
}
|
||||
}
|
||||
this.flyout_.position();
|
||||
};
|
||||
|
@ -187,10 +250,11 @@ Blockly.Toolbox.prototype.position = function() {
|
|||
*/
|
||||
Blockly.Toolbox.prototype.populate_ = function(newTree) {
|
||||
var rootOut = this.tree_;
|
||||
var that = this;
|
||||
rootOut.removeChildren(); // Delete any existing content.
|
||||
rootOut.blocks = [];
|
||||
var hasColours = false;
|
||||
function syncTrees(treeIn, treeOut) {
|
||||
function syncTrees(treeIn, treeOut, pathToMedia) {
|
||||
var lastElement = null;
|
||||
for (var i = 0, childIn; childIn = treeIn.childNodes[i]; i++) {
|
||||
if (!childIn.tagName) {
|
||||
|
@ -201,13 +265,17 @@ Blockly.Toolbox.prototype.populate_ = function(newTree) {
|
|||
case 'CATEGORY':
|
||||
var childOut = rootOut.createNode(childIn.getAttribute('name'));
|
||||
childOut.blocks = [];
|
||||
treeOut.add(childOut);
|
||||
if (that.horizontalLayout_) {
|
||||
treeOut.add(childOut);
|
||||
} else {
|
||||
treeOut.addChildAt(childOut, 0);
|
||||
}
|
||||
var custom = childIn.getAttribute('custom');
|
||||
if (custom) {
|
||||
// Variables and procedures are special dynamic categories.
|
||||
childOut.blocks = custom;
|
||||
} else {
|
||||
syncTrees(childIn, childOut);
|
||||
syncTrees(childIn, childOut, pathToMedia);
|
||||
}
|
||||
var colour = childIn.getAttribute('colour');
|
||||
if (goog.isString(colour)) {
|
||||
|
@ -235,7 +303,13 @@ Blockly.Toolbox.prototype.populate_ = function(newTree) {
|
|||
if (lastElement.tagName.toUpperCase() == 'CATEGORY') {
|
||||
// Separator between two categories.
|
||||
// <sep></sep>
|
||||
treeOut.add(new Blockly.Toolbox.TreeSeparator());
|
||||
if (that.horizontalLayout_) {
|
||||
treeOut.add(new Blockly.Toolbox.TreeSeparator(
|
||||
that.treeSeparatorConfig_));
|
||||
} else {
|
||||
treeOut.addChildAt(new Blockly.Toolbox.TreeSeparator(
|
||||
that.treeSeparatorConfig_), 0);
|
||||
}
|
||||
} else {
|
||||
// Change the gap between two blocks.
|
||||
// <sep gap="36"></sep>
|
||||
|
@ -259,7 +333,7 @@ Blockly.Toolbox.prototype.populate_ = function(newTree) {
|
|||
}
|
||||
}
|
||||
}
|
||||
syncTrees(newTree, this.tree_);
|
||||
syncTrees(newTree, this.tree_, this.workspace_.options.pathToMedia);
|
||||
this.hasColours_ = hasColours;
|
||||
|
||||
if (rootOut.blocks.length) {
|
||||
|
@ -313,16 +387,26 @@ Blockly.Toolbox.prototype.getClientRect = function() {
|
|||
// area are still deleted. Must be smaller than Infinity, but larger than
|
||||
// the largest screen size.
|
||||
var BIG_NUM = 10000000;
|
||||
var toolboxRect = this.HtmlDiv.getBoundingClientRect();
|
||||
|
||||
var x = toolboxRect.left;
|
||||
var y = toolboxRect.top;
|
||||
var width = toolboxRect.width;
|
||||
var height = toolboxRect.height;
|
||||
|
||||
// Assumes that the toolbox is on the SVG edge. If this changes
|
||||
// (e.g. toolboxes in mutators) then this code will need to be more complex.
|
||||
var toolboxRect = this.HtmlDiv.getBoundingClientRect();
|
||||
if (this.workspace_.RTL) {
|
||||
var width = toolboxRect.left + toolboxRect.width + BIG_NUM;
|
||||
return new goog.math.Rect(toolboxRect.left, -BIG_NUM, width, BIG_NUM * 2);
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
||||
return new goog.math.Rect(-BIG_NUM, -BIG_NUM, BIG_NUM + x + width,
|
||||
2 * BIG_NUM);
|
||||
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, 2 * BIG_NUM);
|
||||
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
|
||||
return new goog.math.Rect(-BIG_NUM, -BIG_NUM, 2 * BIG_NUM,
|
||||
BIG_NUM + y + height);
|
||||
} else { // Bottom
|
||||
return new goog.math.Rect(0, y, 2 * BIG_NUM, BIG_NUM + width);
|
||||
}
|
||||
// LTR
|
||||
var width = BIG_NUM + toolboxRect.width + toolboxRect.left;
|
||||
return new goog.math.Rect(-BIG_NUM, -BIG_NUM, width, BIG_NUM * 2);
|
||||
};
|
||||
|
||||
// Extending Closure's Tree UI.
|
||||
|
@ -495,18 +579,7 @@ Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function(e) {
|
|||
* @constructor
|
||||
* @extends {Blockly.Toolbox.TreeNode}
|
||||
*/
|
||||
Blockly.Toolbox.TreeSeparator = function() {
|
||||
Blockly.Toolbox.TreeNode.call(this, null, '',
|
||||
Blockly.Toolbox.TreeSeparator.CONFIG_);
|
||||
Blockly.Toolbox.TreeSeparator = function(config) {
|
||||
Blockly.Toolbox.TreeNode.call(this, null, '', config);
|
||||
};
|
||||
goog.inherits(Blockly.Toolbox.TreeSeparator, Blockly.Toolbox.TreeNode);
|
||||
|
||||
/**
|
||||
* Configuration constants for tree separator.
|
||||
* @type {Object.<string,*>}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.TreeSeparator.CONFIG_ = {
|
||||
cssTreeRow: 'blocklyTreeSeparator'
|
||||
};
|
||||
|
|
|
@ -43,6 +43,8 @@ Blockly.Workspace = function(opt_options) {
|
|||
this.options = opt_options || {};
|
||||
/** @type {boolean} */
|
||||
this.RTL = !!this.options.RTL;
|
||||
/** @type {boolean} */
|
||||
this.horizontalLayout = !!this.options.horizontalLayout;
|
||||
/** @type {!Array.<!Blockly.Block>} */
|
||||
this.topBlocks_ = [];
|
||||
/** @type {!Array.<!Function>} */
|
||||
|
|
|
@ -287,7 +287,9 @@ Blockly.WorkspaceSvg.prototype.addFlyout_ = function() {
|
|||
var workspaceOptions = {
|
||||
disabledPatternId: this.options.disabledPatternId,
|
||||
parentWorkspace: this,
|
||||
RTL: this.RTL
|
||||
RTL: this.RTL,
|
||||
horizontalLayout: this.horizontalLayout,
|
||||
toolboxPosition: this.options.toolboxPosition,
|
||||
};
|
||||
/** @type {Blockly.Flyout} */
|
||||
this.flyout_ = new Blockly.Flyout(workspaceOptions);
|
||||
|
|
462
tests/multi_playground.html
Normal file
462
tests/multi_playground.html
Normal file
|
@ -0,0 +1,462 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Multi-toolbox Playground</title>
|
||||
<script src="../blockly_uncompressed.js"></script>
|
||||
<script src="../msg/messages.js"></script>
|
||||
<script src="../blocks/logic.js"></script>
|
||||
<script src="../blocks/loops.js"></script>
|
||||
<script src="../blocks/math.js"></script>
|
||||
<script src="../blocks/text.js"></script>
|
||||
<script src="../blocks/lists.js"></script>
|
||||
<script src="../blocks/colour.js"></script>
|
||||
<script src="../blocks/variables.js"></script>
|
||||
<script src="../blocks/procedures.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
function start() {
|
||||
startBlocklyInstance('VertStartLTR', false, false, 'start');
|
||||
startBlocklyInstance('VertStartRTL', true, false, 'start');
|
||||
|
||||
startBlocklyInstance('VertEndLTR', false, false, 'end');
|
||||
startBlocklyInstance('VertEndRTL', true, false, 'end');
|
||||
|
||||
|
||||
startBlocklyInstance('HorizontalStartLTR', false, true, 'start');
|
||||
startBlocklyInstance('HorizontalStartRTL', true, true, 'start');
|
||||
|
||||
startBlocklyInstance('HorizontalEndLTR', false, true, 'end');
|
||||
startBlocklyInstance('HorizontalEndRTL', true, true, 'end');
|
||||
}
|
||||
|
||||
function startBlocklyInstance(suffix, rtl, horizontalLayout, position) {
|
||||
var toolbox = document.getElementById('toolbox_alwaysOpen');
|
||||
var options = {
|
||||
comments: false,
|
||||
disable: false,
|
||||
collapse: false,
|
||||
maxBlocks: Infinity,
|
||||
media: '../media/',
|
||||
readOnly: false,
|
||||
rtl: rtl,
|
||||
scrollbars: true,
|
||||
toolbox: toolbox,
|
||||
trashcan: true,
|
||||
horizontalLayout: horizontalLayout,
|
||||
toolboxPosition: position,
|
||||
zoom: {
|
||||
controls: true,
|
||||
wheel: false,
|
||||
startScale: 1.0,
|
||||
maxScale: 4,
|
||||
minScale: 0.25,
|
||||
scaleSpeed: 1.1
|
||||
},
|
||||
};
|
||||
Blockly.inject('blocklyDiv' + suffix, options);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: #fff;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
h1 {
|
||||
font-weight: normal;
|
||||
font-size: 140%;
|
||||
}
|
||||
#blocklyDiv {
|
||||
float: right;
|
||||
height: 95%;
|
||||
width: 70%;
|
||||
}
|
||||
#collaborators {
|
||||
float: right;
|
||||
width: 30px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#collaborators > img {
|
||||
margin-right: 5px;
|
||||
height: 30px;
|
||||
padding-bottom: 5px;
|
||||
width: 30px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
#importExport {
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="start()">
|
||||
|
||||
<div id="collaborators"></div>
|
||||
<table>
|
||||
<tr>
|
||||
<td/>
|
||||
<td>LTR</td>
|
||||
<td>RTL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vertical layout; toolbox at start</td>
|
||||
<td>
|
||||
<div id="blocklyDivVertStartLTR" style="height: 480px; width: 600px;"></div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="blocklyDivVertStartRTL" style="height: 480px; width: 600px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vertical layout; toolbox at end</td>
|
||||
<td>
|
||||
<div id="blocklyDivVertEndLTR" style="height: 480px; width: 600px;"></div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="blocklyDivVertEndRTL" style="height: 480px; width: 600px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Horizontal layout; toolbox at start</td>
|
||||
<td>
|
||||
<div id="blocklyDivHorizontalStartLTR" style="height: 480px; width: 600px;"></div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="blocklyDivHorizontalStartRTL" style="height: 480px; width: 600px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Horizontal layout; toolbox at end</td>
|
||||
<td>
|
||||
<div id="blocklyDivHorizontalEndLTR" style="height: 480px; width: 600px;"></div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="blocklyDivHorizontalEndRTL" style="height: 480px; width: 600px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<xml id="toolbox_alwaysOpen" style="display: none">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<!-- <block type="control_repeat"></block> -->
|
||||
<block type="logic_operation"></block>
|
||||
<block type="controls_repeat_ext">
|
||||
<value name="TIMES">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
|
||||
<xml id="toolbox_categoriesScroll" style="display: none">
|
||||
<category name="Logic" colour="210">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="logic_operation"></block>
|
||||
<block type="logic_negate"></block>
|
||||
<block type="logic_boolean"></block>
|
||||
<block type="logic_null" disabled="true"></block>
|
||||
<block type="logic_ternary"></block>
|
||||
</category>
|
||||
<category name="Loops" colour="120">
|
||||
<block type="controls_repeat_ext">
|
||||
<value name="TIMES">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="controls_repeat" disabled="true"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_for">
|
||||
<value name="FROM">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="TO">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="BY">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="controls_forEach"></block>
|
||||
<block type="controls_flow_statements"></block>
|
||||
</category>
|
||||
<category name="Math" colour="230">
|
||||
<block type="math_number" gap="32"></block>
|
||||
<block type="math_arithmetic">
|
||||
<value name="A">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="B">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_single">
|
||||
<value name="NUM">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">9</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_trig">
|
||||
<value name="NUM">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">45</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_constant"></block>
|
||||
<block type="math_number_property">
|
||||
<value name="NUMBER_TO_CHECK">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_change">
|
||||
<value name="DELTA">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_round">
|
||||
<value name="NUM">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">3.1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_on_list"></block>
|
||||
<block type="math_modulo">
|
||||
<value name="DIVIDEND">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">64</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="DIVISOR">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_constrain">
|
||||
<value name="VALUE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">50</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="LOW">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="HIGH">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">100</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_random_int">
|
||||
<value name="FROM">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="TO">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">100</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="math_random_float"></block>
|
||||
</category>
|
||||
<category name="Text" colour="160">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_append">
|
||||
<value name="TEXT">
|
||||
<shadow type="text"></shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_length">
|
||||
<value name="VALUE">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">abc</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_isEmpty">
|
||||
<value name="VALUE">
|
||||
<shadow type="text">
|
||||
<field name="TEXT"></field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_indexOf">
|
||||
<value name="VALUE">
|
||||
<block type="variables_get">
|
||||
<field name="VAR">text</field>
|
||||
</block>
|
||||
</value>
|
||||
<value name="FIND">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">abc</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_charAt">
|
||||
<value name="VALUE">
|
||||
<block type="variables_get">
|
||||
<field name="VAR">text</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_getSubstring">
|
||||
<value name="STRING">
|
||||
<block type="variables_get">
|
||||
<field name="VAR">text</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_changeCase">
|
||||
<value name="TEXT">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">abc</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_trim">
|
||||
<value name="TEXT">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">abc</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_print">
|
||||
<value name="TEXT">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">abc</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="text_prompt_ext">
|
||||
<value name="TEXT">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">abc</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="Lists" colour="260">
|
||||
<block type="lists_create_with">
|
||||
<mutation items="0"></mutation>
|
||||
</block>
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_repeat">
|
||||
<value name="NUM">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">5</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="lists_length"></block>
|
||||
<block type="lists_isEmpty"></block>
|
||||
<block type="lists_indexOf">
|
||||
<value name="VALUE">
|
||||
<block type="variables_get">
|
||||
<field name="VAR">list</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
<block type="lists_getIndex">
|
||||
<value name="VALUE">
|
||||
<block type="variables_get">
|
||||
<field name="VAR">list</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
<block type="lists_setIndex">
|
||||
<value name="LIST">
|
||||
<block type="variables_get">
|
||||
<field name="VAR">list</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
<block type="lists_getSublist">
|
||||
<value name="LIST">
|
||||
<block type="variables_get">
|
||||
<field name="VAR">list</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
<block type="lists_split">
|
||||
<value name="DELIM">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">,</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="Colour" colour="20">
|
||||
<block type="colour_picker"></block>
|
||||
<block type="colour_random"></block>
|
||||
<block type="colour_rgb">
|
||||
<value name="RED">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">100</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="GREEN">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">50</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="BLUE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="colour_blend">
|
||||
<value name="COLOUR1">
|
||||
<shadow type="colour_picker">
|
||||
<field name="COLOUR">#ff0000</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="COLOUR2">
|
||||
<shadow type="colour_picker">
|
||||
<field name="COLOUR">#3333ff</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="RATIO">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">0.5</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
<sep></sep>
|
||||
<category name="Variables" colour="330" custom="VARIABLE"></category>
|
||||
<category name="Functions" colour="290" custom="PROCEDURE"></category>
|
||||
</xml>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue