Merge pull request #466 from rachel-fenichel/feature/category_with_buttons

Button in flyout
This commit is contained in:
rachel-fenichel
2016-07-01 13:43:44 -07:00
committed by GitHub
15 changed files with 1910 additions and 1715 deletions

View File

@@ -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.

View File

@@ -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
View 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();
};

View File

@@ -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">

View File

@@ -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