Add red 'X' to mouse cursor if blocks are over flyout.

This commit is contained in:
Neil Fraser
2014-11-28 21:43:39 -08:00
parent 208e49c9e6
commit 674625c47e
9 changed files with 174 additions and 112 deletions

View File

@@ -300,6 +300,7 @@ Blockly.Block.terminateDrag_ = function() {
selected.workspace.fireChangeEvent();
}
Blockly.Block.dragMode_ = 0;
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
};
/**
@@ -557,7 +558,6 @@ Blockly.Block.prototype.onMouseDown_ = function(e) {
Blockly.Block.prototype.onMouseUp_ = function(e) {
var this_ = this;
Blockly.doCommand(function() {
Blockly.terminateDrag_();
if (Blockly.selected && Blockly.highlightedConnection_) {
// Connect two blocks together.
Blockly.localConnection_.connect(Blockly.highlightedConnection_);
@@ -572,13 +572,15 @@ Blockly.Block.prototype.onMouseUp_ = function(e) {
}
inferiorConnection.sourceBlock_.svg_.connectionUiEffect();
}
if (this_.workspace.trashcan && this_.workspace.trashcan.isOpen) {
if (this_.workspace.trashcan) {
// Don't throw an object in the trash can if it just got connected.
this_.workspace.trashcan.close();
}
} else if (this_.workspace.trashcan && this_.workspace.trashcan.isOpen) {
} else if (this_.workspace.isDeleteArea(e)) {
var trashcan = this_.workspace.trashcan;
goog.Timer.callOnce(trashcan.close, 100, trashcan);
if (trashcan) {
goog.Timer.callOnce(trashcan.close, 100, trashcan);
}
Blockly.selected.dispose(false, true);
// Dropping a block on the trash can will usually cause the workspace to
// resize to contain the newly positioned block. Force a second resize
@@ -589,6 +591,7 @@ Blockly.Block.prototype.onMouseUp_ = function(e) {
Blockly.highlightedConnection_.unhighlight();
Blockly.highlightedConnection_ = null;
}
Blockly.terminateDrag_();
});
};
@@ -862,6 +865,7 @@ Blockly.Block.prototype.onMouseMove_ = function(e) {
// Push this block to the very top of the stack.
this_.setParent(null);
this_.setDragging_(true);
this_.workspace.recordDeleteAreas();
}
}
if (Blockly.Block.dragMode_ == 2) {
@@ -907,9 +911,10 @@ Blockly.Block.prototype.onMouseMove_ = function(e) {
Blockly.highlightedConnection_ = closestConnection;
Blockly.localConnection_ = localConnection;
}
// Flip the trash can lid if needed.
if (this_.workspace.trashcan && this_.isDeletable()) {
this_.workspace.trashcan.onMouseMove(e);
// Provide visual indication of whether the block will be deleted if
// dropped here.
if (this_.isDeletable()) {
this_.workspace.isDeleteArea(e);
}
}
// This event has been handled. No need to bubble up to the document.

View File

@@ -28,6 +28,7 @@ goog.provide('Blockly.Css');
goog.require('goog.cssom');
/**
* List of cursors.
* @enum {string}
@@ -38,6 +39,12 @@ Blockly.Css.Cursor = {
DELETE: 'handdelete'
};
/**
* Current cursor (cached value).
* @type string
*/
Blockly.Css.currentCursor_ = '';
/**
* Large stylesheet added by Blockly.Css.inject.
* @type Element
@@ -75,9 +82,10 @@ Blockly.Css.inject = function() {
* @param {Blockly.Cursor} cursor Enum.
*/
Blockly.Css.setCursor = function(cursor) {
if (Blockly.readOnly) {
if (Blockly.readOnly || Blockly.Css.currentCursor_ == cursor) {
return;
}
Blockly.Css.currentCursor_ = cursor;
/*
Hotspot coordinates are baked into the CUR file, but they are still
required in the CSS due to a Chrome bug.
@@ -88,10 +96,19 @@ Blockly.Css.setCursor = function(cursor) {
} else {
var xy = '7 3';
}
var rule = '.blocklyDraggable {\n' +
' cursor: url(' + Blockly.Css.mediaPath_ + '/' + cursor + '.cur)' +
' ' + xy + ', auto;\n}\n';
var url = 'url(' + Blockly.Css.mediaPath_ + '/' + cursor +
'.cur) ' + xy + ', auto';
var rule = '.blocklyDraggable {\n cursor: ' + url + ';\n}\n';
goog.cssom.replaceCssRule('', rule, Blockly.Css.styleSheet_, 0);
if (Blockly.svg) {
// Set cursor on the SVG surface as well, so that rapid movements
// don't result in cursor changing to an arrow momentarily.
if (cursor == Blockly.Css.Cursor.OPEN) {
Blockly.svg.style.cursor = '';
} else {
Blockly.svg.style.cursor = url;
}
}
};
/**

View File

@@ -29,6 +29,7 @@ goog.provide('Blockly.Flyout');
goog.require('Blockly.Block');
goog.require('Blockly.Comment');
goog.require('goog.userAgent');
goog.require('goog.math.Rect');
/**
@@ -670,3 +671,20 @@ Blockly.Flyout.terminateDrag_ = function() {
Blockly.Flyout.startBlock_ = null;
Blockly.Flyout.startFlyout_ = null;
};
/**
* Return the deletion rectangle for this flyout.
* @return {goog.math.Rect} Rectangle in which to delete.
*/
Blockly.Flyout.prototype.getRect = function() {
// BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout
// area are still deleted. Must be smaller than Infinity, but larger than
// the largest screen size.
var BIG_NUM = 10000000;
var x = Blockly.getSvgXY_(this.svgGroup_).x;
if (!Blockly.RTL) {
x -= BIG_NUM;
}
return new goog.math.Rect(x, -BIG_NUM,
BIG_NUM + this.width_, this.height_ + 2 * BIG_NUM);
};

View File

@@ -327,11 +327,6 @@ Blockly.createDom_ = function(container) {
if (overflow < 0) {
block.moveBy(overflow, 0);
}
// Delete any block that's sitting on top of the flyout.
if (block.isDeletable() && (Blockly.RTL ?
blockXY.x - metrics.viewWidth : -blockXY.x) > MARGIN * 2) {
block.dispose(false, true);
}
}
}
}

View File

@@ -118,9 +118,9 @@ Blockly.Mutator.prototype.createEditor_ = function() {
var mutator = this;
this.workspace_ = new Blockly.Workspace(
function() {return mutator.getFlyoutMetrics_();}, null);
this.flyout_ = new Blockly.Flyout();
this.flyout_.autoClose = false;
this.svgDialog_.appendChild(this.flyout_.createDom());
this.workspace_.flyout_ = new Blockly.Flyout();
this.workspace_.flyout_.autoClose = false;
this.svgDialog_.appendChild(this.workspace_.flyout_.createDom());
this.svgDialog_.appendChild(this.workspace_.createDom());
return this.svgDialog_;
};
@@ -148,7 +148,7 @@ Blockly.Mutator.prototype.updateEditable = function() {
Blockly.Mutator.prototype.resizeBubble_ = function() {
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
var workspaceSize = this.workspace_.getCanvas().getBBox();
var flyoutMetrics = this.flyout_.getMetrics_();
var flyoutMetrics = this.workspace_.flyout_.getMetrics_();
var width;
if (Blockly.RTL) {
width = -workspaceSize.x;
@@ -193,8 +193,8 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
this.createEditor_(), this.block_.svg_.svgPath_,
this.iconX_, this.iconY_, null, null);
var thisObj = this;
this.flyout_.init(this.workspace_);
this.flyout_.show(this.quarkXml_);
this.workspace_.flyout_.init(this.workspace_);
this.workspace_.flyout_.show(this.quarkXml_);
this.rootBlock_ = this.block_.decompose(this.workspace_);
var blocks = this.rootBlock_.getDescendants();
@@ -204,8 +204,8 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
// The root block should not be dragable or deletable.
this.rootBlock_.setMovable(false);
this.rootBlock_.setDeletable(false);
var margin = this.flyout_.CORNER_RADIUS * 2;
var x = this.flyout_.width_ + margin;
var margin = this.workspace_.flyout_.CORNER_RADIUS * 2;
var x = this.workspace_.flyout_.width_ + margin;
if (Blockly.RTL) {
x = -x;
}
@@ -226,8 +226,6 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
} else {
// Dispose of the bubble.
this.svgDialog_ = null;
this.flyout_.dispose();
this.flyout_ = null;
this.workspace_.dispose();
this.workspace_ = null;
this.rootBlock_ = null;
@@ -244,7 +242,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
/**
* Update the source block when the mutator's blocks are changed.
* Delete or bump any block that's out of bounds.
* Bump down any block that's too high.
* Fired whenever a change is made to the mutator's workspace.
* @private
*/
@@ -255,12 +253,7 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() {
for (var b = 0, block; block = blocks[b]; b++) {
var blockXY = block.getRelativeToSurfaceXY();
var blockHW = block.getHeightWidth();
if (block.isDeletable() && (Blockly.RTL ?
blockXY.x > -this.flyout_.width_ + MARGIN :
blockXY.x < this.flyout_.width_ - MARGIN)) {
// Delete any block that's sitting on top of the flyout.
block.dispose(false, true);
} else if (blockXY.y + blockHW.height < MARGIN) {
if (blockXY.y + blockHW.height < MARGIN) {
// Bump any block that's above the top back inside.
block.moveBy(0, MARGIN - blockHW.height - blockXY.y);
}

View File

@@ -27,6 +27,7 @@
goog.provide('Blockly.Trashcan');
goog.require('goog.math');
goog.require('goog.math.Rect');
goog.require('goog.Timer');
@@ -238,32 +239,16 @@ Blockly.Trashcan.prototype.position_ = function() {
};
/**
* Determines if the mouse is currently over the trash can.
* Opens/closes the lid and sets the isOpen flag.
* @param {!Event} e Mouse move event.
* Return the deletion rectangle for this trashcan.
* @return {goog.math.Rect} Rectangle in which to delete.
*/
Blockly.Trashcan.prototype.onMouseMove = function(e) {
/*
An alternative approach would be to use onMouseOver and onMouseOut events.
However the selected block will be between the mouse and the trash can,
thus these events won't fire.
Another approach is to use HTML5's drag & drop API, but it's widely hated.
Instead, we'll just have the block's drag_ function call us.
*/
if (!this.svgGroup_) {
return;
}
var mouseXY = Blockly.mouseToSvg(e);
Blockly.Trashcan.prototype.getRect = function() {
var trashXY = Blockly.getSvgXY_(this.svgGroup_);
var over = (mouseXY.x > trashXY.x - this.MARGIN_HOTSPOT_) &&
(mouseXY.x < trashXY.x + this.WIDTH_ + this.MARGIN_HOTSPOT_) &&
(mouseXY.y > trashXY.y - this.MARGIN_HOTSPOT_) &&
(mouseXY.y < trashXY.y + this.BODY_HEIGHT_ + this.LID_HEIGHT_ +
this.MARGIN_HOTSPOT_);
// For bonus points we might want to match the trapezoidal outline.
if (this.isOpen != over) {
this.setOpen_(over);
}
return new goog.math.Rect(
trashXY.x - this.MARGIN_HOTSPOT_,
trashXY.y - this.MARGIN_HOTSPOT_,
this.WIDTH_ + 2 * this.MARGIN_HOTSPOT_,
this.BODY_HEIGHT_ + this.LID_HEIGHT_ + 2 * this.MARGIN_HOTSPOT_);
};
/**
@@ -278,8 +263,6 @@ Blockly.Trashcan.prototype.setOpen_ = function(state) {
goog.Timer.clear(this.lidTask_);
this.isOpen = state;
this.animateLid_();
Blockly.Css.setCursor(state ? Blockly.Css.Cursor.DELETE :
Blockly.Css.Cursor.CLOSED);
};
/**

View File

@@ -32,6 +32,7 @@ goog.require('Blockly.ScrollbarPair');
goog.require('Blockly.Trashcan');
goog.require('Blockly.Xml');
goog.require('goog.math');
goog.require('goog.math.Coordinate');
/**
@@ -134,6 +135,10 @@ Blockly.Workspace.prototype.dispose = function() {
}
this.svgBlockCanvas_ = null;
this.svgBubbleCanvas_ = null;
if (this.flyout_) {
this.flyout_.dispose();
this.flyout_ = null;
}
if (this.trashcan) {
this.trashcan.dispose();
this.trashcan = null;
@@ -396,5 +401,49 @@ Blockly.Workspace.prototype.remainingCapacity = function() {
return this.maxBlocks - this.getAllBlocks().length;
};
/**
* Make a list of all the delete areas for this workspace.
*/
Blockly.Workspace.prototype.recordDeleteAreas = function() {
if (this.trashcan) {
this.deleteAreaTrash_ = this.trashcan.getRect();
} else {
this.deleteAreaTrash_ = null;
}
if (this.flyout_) {
this.deleteAreaToolbox_ = this.flyout_.getRect();
} else {
this.deleteAreaToolbox_ = null;
}
};
/**
* Is the mouse event over a delete area (toolbar or non-closing flyout)?
* Opens or closes the trashcan and sets the cursor as a side effect.
* @param {!Event} e Mouse move event.
* @return {boolean} True if event is in a delete area.
*/
Blockly.Workspace.prototype.isDeleteArea = function(e) {
var isDelete = false;
var mouseXY = Blockly.mouseToSvg(e);
var xy = new goog.math.Coordinate(mouseXY.x, mouseXY.y);
if (this.deleteAreaTrash_) {
if (this.deleteAreaTrash_.contains(xy)) {
this.trashcan.setOpen_(true);
Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE);
return true;
}
this.trashcan.setOpen_(false);
}
if (this.deleteAreaToolbox_) {
if (this.deleteAreaToolbox_.contains(xy)) {
Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE);
return true;
}
}
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
return false;
};
// Export symbols that would otherwise be renamed by Closure compiler.
Blockly.Workspace.prototype['clear'] = Blockly.Workspace.prototype.clear;