mirror of
https://github.com/google/blockly.git
synced 2026-01-18 06:17:12 +01:00
Merge pull request #466 from rachel-fenichel/feature/category_with_buttons
Button in flyout
This commit is contained in:
@@ -244,6 +244,15 @@ Blockly.Css.CONTENT = [
|
||||
'fill: #000;',
|
||||
'}',
|
||||
|
||||
'.blocklyFlyoutButton {',
|
||||
'fill: #888;',
|
||||
'cursor: default',
|
||||
'}',
|
||||
|
||||
'.blocklyFlyoutButton:hover {',
|
||||
'fill: #ccc;',
|
||||
'}',
|
||||
|
||||
/*
|
||||
Don't allow users to select text. It gets annoying when trying to
|
||||
drag a block and selected text moves instead.
|
||||
|
||||
140
core/flyout.js
140
core/flyout.js
@@ -29,6 +29,7 @@ goog.provide('Blockly.Flyout');
|
||||
goog.require('Blockly.Block');
|
||||
goog.require('Blockly.Comment');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.FlyoutButton');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
@@ -84,6 +85,11 @@ Blockly.Flyout = function(workspaceOptions) {
|
||||
* @type {!Array.<!Element>}
|
||||
* @private
|
||||
*/
|
||||
this.backgroundButtons_ = [];
|
||||
|
||||
/**
|
||||
* List of visible buttons.
|
||||
*/
|
||||
this.buttons_ = [];
|
||||
|
||||
/**
|
||||
@@ -549,24 +555,33 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
||||
|
||||
this.svgGroup_.style.display = 'block';
|
||||
// Create the blocks to be shown in this flyout.
|
||||
var blocks = [];
|
||||
var contents = [];
|
||||
var gaps = [];
|
||||
this.permanentlyDisabled_.length = 0;
|
||||
for (var i = 0, xml; xml = xmlList[i]; i++) {
|
||||
if (xml.tagName && xml.tagName.toUpperCase() == 'BLOCK') {
|
||||
var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_);
|
||||
if (curBlock.disabled) {
|
||||
// Record blocks that were initially disabled.
|
||||
// Do not enable these blocks as a result of capacity filtering.
|
||||
this.permanentlyDisabled_.push(curBlock);
|
||||
if (xml.tagName) {
|
||||
var tagName = xml.tagName.toUpperCase();
|
||||
if (tagName == 'BLOCK') {
|
||||
var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_);
|
||||
if (curBlock.disabled) {
|
||||
// Record blocks that were initially disabled.
|
||||
// Do not enable these blocks as a result of capacity filtering.
|
||||
this.permanentlyDisabled_.push(curBlock);
|
||||
}
|
||||
contents.push({type: 'block', block: curBlock});
|
||||
var gap = parseInt(xml.getAttribute('gap'), 10);
|
||||
gaps.push(isNaN(gap) ? this.MARGIN * 3 : gap);
|
||||
}
|
||||
else if (tagName == 'BUTTON') {
|
||||
var label = xml.getAttribute('text');
|
||||
var curButton = new Blockly.FlyoutButton(this.workspace_, label);
|
||||
contents.push({type: 'button', button: curButton});
|
||||
gaps.push(this.MARGIN);
|
||||
}
|
||||
blocks.push(curBlock);
|
||||
var gap = parseInt(xml.getAttribute('gap'), 10);
|
||||
gaps.push(isNaN(gap) ? this.MARGIN * 3 : gap);
|
||||
}
|
||||
}
|
||||
|
||||
this.layoutBlocks_(blocks, gaps);
|
||||
this.layout_(contents, gaps);
|
||||
|
||||
// IE 11 is an incompetent browser that fails to fire mouseout events.
|
||||
// When the mouse is over the background, deselect all blocks.
|
||||
@@ -598,56 +613,69 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
||||
|
||||
/**
|
||||
* Lay out the blocks in the flyout.
|
||||
* @param {!Array.<!Blockly.BlockSvg>} blocks The blocks to lay out.
|
||||
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
|
||||
* @param {!Array.<number>} gaps The visible gaps between blocks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps) {
|
||||
Blockly.Flyout.prototype.layout_ = function(contents, gaps) {
|
||||
this.workspace_.scale = this.targetWorkspace_.scale;
|
||||
var margin = this.MARGIN;
|
||||
var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH;
|
||||
var cursorY = margin;
|
||||
if (this.horizontalLayout_ && this.RTL) {
|
||||
blocks = blocks.reverse();
|
||||
contents = contents.reverse();
|
||||
}
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
var allBlocks = block.getDescendants();
|
||||
for (var j = 0, child; child = allBlocks[j]; j++) {
|
||||
// Mark blocks as being inside a flyout. This is used to detect and
|
||||
// prevent the closure of the flyout if the user right-clicks on such a
|
||||
// block.
|
||||
child.isInFlyout = true;
|
||||
}
|
||||
block.render();
|
||||
var root = block.getSvgRoot();
|
||||
var blockHW = block.getHeightWidth();
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += tab;
|
||||
}
|
||||
|
||||
if (this.horizontalLayout_ && this.RTL) {
|
||||
block.moveBy(cursorX + blockHW.width - tab, cursorY);
|
||||
} else {
|
||||
block.moveBy(cursorX, cursorY);
|
||||
for (var i = 0, item; item = contents[i]; i++) {
|
||||
if (item.type == 'block') {
|
||||
var block = item.block;
|
||||
var allBlocks = block.getDescendants();
|
||||
for (var j = 0, child; child = allBlocks[j]; j++) {
|
||||
// Mark blocks as being inside a flyout. This is used to detect and
|
||||
// prevent the closure of the flyout if the user right-clicks on such a
|
||||
// block.
|
||||
child.isInFlyout = true;
|
||||
}
|
||||
block.render();
|
||||
var root = block.getSvgRoot();
|
||||
var blockHW = block.getHeightWidth();
|
||||
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += tab;
|
||||
}
|
||||
block.moveBy((this.horizontalLayout_ && this.RTL) ? -cursorX : cursorX,
|
||||
cursorY);
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += (blockHW.width + gaps[i] - tab);
|
||||
} 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.
|
||||
var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null);
|
||||
rect.tooltip = block;
|
||||
Blockly.Tooltip.bindMouseEvents(rect);
|
||||
// Add the rectangles under the blocks, so that the blocks' tooltips work.
|
||||
this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot());
|
||||
block.flyoutRect_ = rect;
|
||||
this.backgroundButtons_[i] = rect;
|
||||
|
||||
this.addBlockListeners_(root, block, rect);
|
||||
} else if (item.type == 'button') {
|
||||
var button = item.button;
|
||||
var buttonSvg = button.createDom();
|
||||
button.moveTo(cursorX, cursorY);
|
||||
button.show();
|
||||
Blockly.bindEvent_(buttonSvg, 'mouseup', button, button.onMouseUp);
|
||||
|
||||
this.buttons_.push(button);
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += (button.width + gaps[i]);
|
||||
} else {
|
||||
cursorY += button.height + gaps[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.horizontalLayout_) {
|
||||
cursorX += (blockHW.width + gaps[i] - tab);
|
||||
} 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.
|
||||
var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null);
|
||||
rect.tooltip = block;
|
||||
Blockly.Tooltip.bindMouseEvents(rect);
|
||||
// Add the rectangles under the blocks, so that the blocks' tooltips work.
|
||||
this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot());
|
||||
block.flyoutRect_ = rect;
|
||||
this.buttons_[i] = rect;
|
||||
|
||||
this.addBlockListeners_(root, block, rect);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -664,9 +692,14 @@ Blockly.Flyout.prototype.clearOldBlocks_ = function() {
|
||||
}
|
||||
}
|
||||
// Delete any background buttons from a previous showing.
|
||||
for (var j = 0, rect; rect = this.buttons_[j]; j++) {
|
||||
for (var j = 0, rect; rect = this.backgroundButtons_[j]; j++) {
|
||||
goog.dom.removeNode(rect);
|
||||
}
|
||||
this.backgroundButtons_.length = 0;
|
||||
|
||||
for (var i = 0, button; button = this.buttons_[i]; i++) {
|
||||
button.dispose();
|
||||
}
|
||||
this.buttons_.length = 0;
|
||||
};
|
||||
|
||||
@@ -1070,6 +1103,9 @@ Blockly.Flyout.prototype.reflowVertical = function(blocks) {
|
||||
}
|
||||
flyoutWidth = Math.max(flyoutWidth, width);
|
||||
}
|
||||
for (var i = 0, button; button = this.buttons_[i]; i++) {
|
||||
flyoutWidth = Math.max(flyoutWidth, button.width);
|
||||
}
|
||||
flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH;
|
||||
flyoutWidth *= this.workspace_.scale;
|
||||
flyoutWidth += Blockly.Scrollbar.scrollbarThickness;
|
||||
|
||||
158
core/flyout_button.js
Normal file
158
core/flyout_button.js
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 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 Object representing an icon on a block.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FlyoutButton');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a button in the flyout.
|
||||
* @param {!Blockly.Workspace} workspace The workspace in which to place this
|
||||
* button.
|
||||
* @param {string} text The text to display on the button.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FlyoutButton = function(workspace, text) {
|
||||
/**
|
||||
* @type {!Blockly.Workspace}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.text_ = text;
|
||||
|
||||
/**
|
||||
* @type {goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.position_ = new goog.math.Coordinate(0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* The margin around the text in the button.
|
||||
*/
|
||||
Blockly.FlyoutButton.MARGIN = 5;
|
||||
|
||||
/**
|
||||
* The width of the button's rect.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.width = 0;
|
||||
|
||||
/**
|
||||
* The height of the button's rect.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.height = 0;
|
||||
|
||||
/**
|
||||
* Create the button elements.
|
||||
* @return {!Element} The button's SVG group.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.createDom = function() {
|
||||
this.svgGroup_ = Blockly.createSvgElement('g',
|
||||
{'class': 'blocklyFlyoutButton'}, this.workspace_.getCanvas());
|
||||
|
||||
// Rect with rounded corners.
|
||||
var rect = Blockly.createSvgElement('rect',
|
||||
{'rx': 4, 'ry': 4,
|
||||
'height': 0, 'width': 0},
|
||||
this.svgGroup_);
|
||||
|
||||
var svgText = Blockly.createSvgElement('text',
|
||||
{'class': 'blocklyText', 'x': 0, 'y': 0,
|
||||
'text-anchor': 'middle'}, this.svgGroup_);
|
||||
svgText.textContent = this.text_;
|
||||
|
||||
this.width = svgText.getComputedTextLength() +
|
||||
2 * Blockly.FlyoutButton.MARGIN;
|
||||
this.height = 20; // Can't compute it :(
|
||||
|
||||
rect.setAttribute('width', this.width);
|
||||
rect.setAttribute('height', this.height);
|
||||
|
||||
svgText.setAttribute('x', this.width / 2);
|
||||
svgText.setAttribute('y', this.height - Blockly.FlyoutButton.MARGIN);
|
||||
|
||||
this.updateTransform_();
|
||||
return this.svgGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Correctly position the flyout button and make it visible.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.show = function() {
|
||||
this.updateTransform_();
|
||||
this.svgGroup_.setAttribute('display', 'block');
|
||||
};
|
||||
|
||||
/**
|
||||
* Update svg attributes to match internal state.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.updateTransform_ = function() {
|
||||
this.svgGroup_.setAttribute('transform', 'translate(' + this.position_.x +
|
||||
',' + this.position_.y + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the button to the given x, y coordinates.
|
||||
* @param {number} x The new x coordinate.
|
||||
* @param {number} y The new y coordinate.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.moveTo = function(x, y) {
|
||||
this.position_.x = x;
|
||||
this.position_.y = y;
|
||||
this.updateTransform_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this button.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.dispose = function() {
|
||||
if (this.svgGroup_) {
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
this.svgGroup_ = null;
|
||||
}
|
||||
this.workspace_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Do something when the button is clicked.
|
||||
* @param {!Event} e Mouse up event.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.onMouseUp = function(e) {
|
||||
console.log("Button was clicked");
|
||||
// Don't scroll the page.
|
||||
e.preventDefault();
|
||||
// Don't propagate mousewheel event (zooming).
|
||||
e.stopPropagation();
|
||||
};
|
||||
@@ -103,6 +103,9 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
||||
variableList.unshift(Blockly.Msg.VARIABLES_DEFAULT_NAME);
|
||||
|
||||
var xmlList = [];
|
||||
var button = goog.dom.createDom('button');
|
||||
button.setAttribute('text', 'Create variable');
|
||||
xmlList.push(button);
|
||||
for (var i = 0; i < variableList.length; i++) {
|
||||
if (Blockly.Blocks['variables_set']) {
|
||||
// <block type="variables_set" gap="8">
|
||||
|
||||
@@ -216,13 +216,16 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
||||
if (this.options.zoomOptions && this.options.zoomOptions.controls) {
|
||||
bottom = this.addZoomControls_(bottom);
|
||||
}
|
||||
Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_);
|
||||
var thisWorkspace = this;
|
||||
Blockly.bindEvent_(this.svgGroup_, 'touchstart', null,
|
||||
function(e) {Blockly.longStart_(e, thisWorkspace);});
|
||||
if (this.options.zoomOptions && this.options.zoomOptions.wheel) {
|
||||
// Mouse-wheel.
|
||||
Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.onMouseWheel_);
|
||||
|
||||
if (!this.isFlyout) {
|
||||
Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_);
|
||||
var thisWorkspace = this;
|
||||
Blockly.bindEvent_(this.svgGroup_, 'touchstart', null,
|
||||
function(e) {Blockly.longStart_(e, thisWorkspace);});
|
||||
if (this.options.zoomOptions && this.options.zoomOptions.wheel) {
|
||||
// Mouse-wheel.
|
||||
Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.onMouseWheel_);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if there needs to be a category tree, or a simple list of
|
||||
|
||||
Reference in New Issue
Block a user