Skip to content

Commit

Permalink
Movement Updates (#2247)
Browse files Browse the repository at this point in the history
This is a squash and merge of a large set of changes by @BeksOmega 

* Added functionality to scrolling, dragging, and zooming.

* Fixed incorrect changes to workspaceChanged function.

* Fixed comment.

* Fixed typo.

* Removed scrollbar.set calls from workspace_svg.

* Removed scrollbar.resize() call.

* Added move options to playground.

* Fixed scroll_ calls that replaced scrollbar.set calls.

* Removed this.scrollbar checks.

* Changed zoom so that it always zooms towards the coordinates. Changed isContentBounded_ to be separate from isMovable_ (b/c of the previous change zoomControls had to be added to the bounded check). Fixed scroll_() calls... again.

* Changed procedures so the Highlight definition option is only available if the workspace is moveable.

* Fixed scrollCenter so that it works with flyout toolboxes.

* Fixed zoomToFit so that it works with horizontal flyout toolboxes.

* Fixed Typo.

* Fixed bumping blocks when the workspace is not movable.

* Fixed bumping not working with left and top toolbox positions.

* Re-Added not allowing scrollCenter if the workspace is not movable. Disabled scrollCenter button for this case.

* Cleaned up formatting.

* Fixed bumping... again. Reformatted workspaceChanged a bit.

* Changed blocks to be completely bumped into the workspace.

* Reorganized metrics-getting for workspaceChanged.

* Added bumping workspace comments. Moved event checking.

* Renamed workspaceChanged to bumpObjects.

* Added a bumpObjects developer reminder.

* Added warning to zoomToFit.

* Cleaned up some text.

* Added better inline documentation.

* Fixed up inline docs.

* Cleaned up comments.

* Fixed zoomCenter not actually zooming towards the center.

* Fixed zoomControls error on unmovable bottom-toolbox workspaces

* Fixed programatically placing blocks in an unmovable workspace.

* Removed unnecessary translate call in inject.

* Reversed removal of translate. (apparently it was necessary)

* Cleaned up code in response to first round of reviews.

* Added unit comments to the zoom function.

* Removed bumpObjectsEventChecker. Added BUMP_EVENTS list to Blockly.Events.

* Changed getWorkspaceObjectMetrics call to getBoundingRectangle().

* Fixed utils.mouseToSvg (was causing problems with zoom on wheel if the page was scrolled).

* Fixed zoom when page is scrolled (actually this time). Reverted changes to utils.mouseToSvg.

* Fixed centerOnBlock.

* Added unit docs to translate. Moved setting the grid position to the translate function.

* Added TODO's.
  • Loading branch information
BeksOmega authored and rachel-fenichel committed Feb 18, 2019
1 parent c2447e7 commit 9dec2da
Show file tree
Hide file tree
Showing 8 changed files with 507 additions and 198 deletions.
6 changes: 6 additions & 0 deletions blocks/procedures.js
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,12 @@ Blockly.Blocks['procedures_callnoreturn'] = {
* @this Blockly.Block
*/
customContextMenu: function(options) {
if (!this.workspace.isMovable()) {
// If we center on the block and the workspace isn't movable we could
// loose blocks at the edges of the workspace.
return;
}

var option = {enabled: true};
option.text = Blockly.Msg['PROCEDURES_HIGHLIGHT_DEF'];
var name = this.getProcedureCall();
Expand Down
15 changes: 15 additions & 0 deletions core/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ Blockly.Events.COMMENT_MOVE = 'comment_move';
*/
Blockly.Events.FINISHED_LOADING = 'finished_loading';

/**
* List of events that cause objects to be bumped back into the visible
* portion of the workspace (only used for non-movable workspaces).
*
* Not to be confused with bumping so that disconnected connections to do
* not appear connected.
* @const
*/
Blockly.Events.BUMP_EVENTS = [
Blockly.Events.BLOCK_CREATE,
Blockly.Events.BLOCK_MOVE,
Blockly.Events.COMMENT_CREATE,
Blockly.Events.COMMENT_MOVE
];

/**
* List of events queued for firing.
* @private
Expand Down
176 changes: 118 additions & 58 deletions core/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,71 +232,112 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
mainWorkspace.translate(0, 0);
Blockly.mainWorkspace = mainWorkspace;

if (!options.readOnly && !options.hasScrollbars) {
var workspaceChanged = function(e) {
if (!mainWorkspace.isDragging()) {
var metrics = mainWorkspace.getMetrics();
var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
var edgeTop = metrics.viewTop + metrics.absoluteTop;
if (metrics.contentTop < edgeTop ||
metrics.contentTop + metrics.contentHeight >
metrics.viewHeight + edgeTop ||
metrics.contentLeft <
(options.RTL ? metrics.viewLeft : edgeLeft) ||
metrics.contentLeft + metrics.contentWidth > (options.RTL ?
metrics.viewWidth : metrics.viewWidth + edgeLeft)) {
// One or more blocks may be out of bounds. Bump them back in.
var MARGIN = 25;
var blocks = mainWorkspace.getTopBlocks(false);
if (!options.readOnly && !mainWorkspace.isMovable()) {
// Helper function for the workspaceChanged callback.
// TODO (#2300): Move metrics math back to the WorkspaceSvg.
var getWorkspaceMetrics = function() {
var workspaceMetrics = Object.create(null);
var defaultMetrics = mainWorkspace.getMetrics();
var scale = mainWorkspace.scale;

// Get the view metrics in workspace units.
workspaceMetrics.viewLeft = defaultMetrics.viewLeft / scale;
workspaceMetrics.viewTop = defaultMetrics.viewTop / scale;
workspaceMetrics.viewRight =
(defaultMetrics.viewLeft + defaultMetrics.viewWidth) / scale;
workspaceMetrics.viewBottom =
(defaultMetrics.viewTop + defaultMetrics.viewHeight) / scale;

// Get the exact content metrics (in workspace units), even if the
// content is bounded.
if (mainWorkspace.isContentBounded()) {
// Already in workspace units, no need to divide by scale.
var blocksBoundingBox = mainWorkspace.getBlocksBoundingBox();
workspaceMetrics.contentLeft = blocksBoundingBox.x;
workspaceMetrics.contentTop = blocksBoundingBox.y;
workspaceMetrics.contentRight =
blocksBoundingBox.x + blocksBoundingBox.width;
workspaceMetrics.contentBottom =
blocksBoundingBox.y + blocksBoundingBox.height;
} else {
workspaceMetrics.contentLeft = defaultMetrics.contentLeft / scale;
workspaceMetrics.contentTop = defaultMetrics.contentTop / scale;
workspaceMetrics.contentRight =
(defaultMetrics.contentLeft + defaultMetrics.contentWidth) / scale;
workspaceMetrics.contentBottom =
(defaultMetrics.contentTop + defaultMetrics.contentHeight) / scale;
}

return workspaceMetrics;
};

var bumpObjects = function(e) {
// We always check isMovable_ again because the original
// "not movable" state of isMovable_ could have been changed.
if (!mainWorkspace.isDragging() && !mainWorkspace.isMovable() &&
(Blockly.Events.BUMP_EVENTS.indexOf(e.type) != -1)) {
var metrics = getWorkspaceMetrics();
if (metrics.contentTop < metrics.viewTop ||
metrics.contentBottom > metrics.viewBottom ||
metrics.contentLeft < metrics.viewLeft ||
metrics.contentRight > metrics.viewRight) {

// Handle undo.
var oldGroup = null;
if (e) {
oldGroup = Blockly.Events.getGroup();
Blockly.Events.setGroup(e.group);
}
var movedBlocks = false;
for (var b = 0, block; block = blocks[b]; b++) {
var blockXY = block.getRelativeToSurfaceXY();
var blockHW = block.getHeightWidth();
// Bump any block that's above the top back inside.
var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y;
if (overflowTop > 0) {
block.moveBy(0, overflowTop);
movedBlocks = true;
}
// Bump any block that's below the bottom back inside.
var overflowBottom =
edgeTop + metrics.viewHeight - MARGIN - blockXY.y;
if (overflowBottom < 0) {
block.moveBy(0, overflowBottom);
movedBlocks = true;
}
// Bump any block that's off the left back inside.
var overflowLeft = MARGIN + edgeLeft -
blockXY.x - (options.RTL ? 0 : blockHW.width);
if (overflowLeft > 0) {
block.moveBy(overflowLeft, 0);
movedBlocks = true;
}
// Bump any block that's off the right back inside.
var overflowRight = edgeLeft + metrics.viewWidth - MARGIN -
blockXY.x + (options.RTL ? blockHW.width : 0);
if (overflowRight < 0) {
block.moveBy(overflowRight, 0);
movedBlocks = true;
}

switch (e.type) {
case Blockly.Events.BLOCK_CREATE:
case Blockly.Events.BLOCK_MOVE:
var object = mainWorkspace.getBlockById(e.blockId);
break;
case Blockly.Events.COMMENT_CREATE:
case Blockly.Events.COMMENT_MOVE:
var object = mainWorkspace.getCommentById(e.commentId);
break;
}
var objectMetrics = object.getBoundingRectangle();

// Bump any object that's above the top back inside.
var overflowTop = metrics.viewTop - objectMetrics.topLeft.y;
if (overflowTop > 0) {
object.moveBy(0, overflowTop);
}

// Bump any object that's below the bottom back inside.
var overflowBottom = metrics.viewBottom - objectMetrics.bottomRight.y;
if (overflowBottom < 0) {
object.moveBy(0, overflowBottom);
}

// Bump any object that's off the left back inside.
var overflowLeft = metrics.viewLeft - objectMetrics.topLeft.x;
if (overflowLeft > 0) {
object.moveBy(overflowLeft, 0);
}

// Bump any object that's off the right back inside.
var overflowRight = metrics.viewRight - objectMetrics.bottomRight.x;
if (overflowRight < 0) {
object.moveBy(overflowRight, 0);
}

if (e) {
if (!e.group && movedBlocks) {
console.log('WARNING: Moved blocks in bounds but there was no event group.'
+ ' This may break undo.');
if (!e.group) {
console.log('WARNING: Moved object in bounds but there was no' +
' event group. This may break undo.');
}
Blockly.Events.setGroup(oldGroup);
}
}
}
};
mainWorkspace.addChangeListener(workspaceChanged);
mainWorkspace.addChangeListener(bumpObjects);
}

// The SVG is now fully assembled.
Blockly.svgResize(mainWorkspace);
Blockly.WidgetDiv.createDom();
Expand Down Expand Up @@ -339,12 +380,6 @@ Blockly.init_ = function(mainWorkspace) {
mainWorkspace.flyout_.init(mainWorkspace);
mainWorkspace.flyout_.show(options.languageTree.childNodes);
mainWorkspace.flyout_.scrollToStart();
// Translate the workspace sideways to avoid the fixed flyout.
mainWorkspace.scrollX = mainWorkspace.flyout_.width_;
if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
mainWorkspace.scrollX *= -1;
}
mainWorkspace.translate(mainWorkspace.scrollX, 0);
}
}

Expand All @@ -356,9 +391,31 @@ Blockly.init_ = function(mainWorkspace) {
mainWorkspace.zoomControls_.init(verticalSpacing);
}

if (options.hasScrollbars) {
if (options.moveOptions && options.moveOptions.scrollbars) {
mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace);
mainWorkspace.scrollbar.resize();
} else {
mainWorkspace.setMetrics({x: .5, y: .5});
}

if (mainWorkspace.flyout_) {
// Translate the workspace sideways to avoid the fixed flyout.
switch (mainWorkspace.toolboxPosition) {
case Blockly.TOOLBOX_AT_LEFT:
mainWorkspace.scrollX =
mainWorkspace.RTL ? 0 : mainWorkspace.flyout_.width_;
break;
case Blockly.TOOLBOX_AT_RIGHT:
mainWorkspace.scrollX =
mainWorkspace.RTL ? -mainWorkspace.flyout_.width_ : 0;
break;
case Blockly.TOOLBOX_AT_TOP:
mainWorkspace.scrollY = mainWorkspace.flyout_.height_;
break;
// If the toolbox is at the top left (workspace origin) is untouched,
// so no need to include it.
}
mainWorkspace.translate(mainWorkspace.scrollX, mainWorkspace.scrollY);
}

// Load the sounds.
Expand All @@ -380,6 +437,9 @@ Blockly.init_ = function(mainWorkspace) {
*/
Blockly.inject.bindDocumentEvents_ = function() {
if (!Blockly.documentEventsBound_) {
Blockly.bindEventWithChecks_(document, 'scroll', null,
Blockly.mainWorkspace.updateInverseScreenCTM
.bind(Blockly.mainWorkspace));
Blockly.bindEventWithChecks_(document, 'keydown', null, Blockly.onKeyDown_);
// longStop needs to run to stop the context menu from showing up. It
// should run regardless of what other touch event handlers have run.
Expand Down
42 changes: 37 additions & 5 deletions core/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,6 @@ Blockly.Options = function(options) {
Blockly.TOOLBOX_AT_RIGHT : Blockly.TOOLBOX_AT_LEFT;
}

var hasScrollbars = options['scrollbars'];
if (hasScrollbars === undefined) {
hasScrollbars = hasCategories;
}
var hasCss = options['css'];
if (hasCss === undefined) {
hasCss = true;
Expand Down Expand Up @@ -135,7 +131,9 @@ Blockly.Options = function(options) {
this.maxInstances = options['maxInstances'];
this.pathToMedia = pathToMedia;
this.hasCategories = hasCategories;
this.hasScrollbars = hasScrollbars;
this.moveOptions = Blockly.Options.parseMoveOptions(options, hasCategories);
/** @deprecated January 2019 */
this.hasScrollbars = this.moveOptions.scrollbars;
this.hasTrashcan = hasTrashcan;
this.maxTrashcanContents = maxTrashcanContents;
this.hasSounds = hasSounds;
Expand Down Expand Up @@ -165,6 +163,40 @@ Blockly.Options.prototype.setMetrics = null;
*/
Blockly.Options.prototype.getMetrics = null;

/**
* Parse the user-specified move options, using reasonable defaults where
* behavior is unspecified.
* @param {!Object} options Dictionary of options.
* @param {!boolean} hasCategories Whether the workspace has categories or not.
* @return {!Object} A dictionary of normalized options.
* @private
*/
Blockly.Options.parseMoveOptions = function(options, hasCategories) {
var move = options['move'] || {};
var moveOptions = {};
if (move['scrollbars'] === undefined
&& options['scrollbars'] === undefined) {
moveOptions.scrollbars = hasCategories;
} else {
moveOptions.scrollbars = !!move['scrollbars'] || !!options['scrollbars'];
}
if (!moveOptions.scrollbars || move['wheel'] === undefined) {
// Defaults to false so that developers' settings don't appear to change.
moveOptions.wheel = false;
} else {
moveOptions.wheel = !!move['wheel'];
}
if (!moveOptions.scrollbars) {
moveOptions.drag = false;
} else if (move['drag'] === undefined) {
// Defaults to true if scrollbars is true.
moveOptions.drag = true;
} else {
moveOptions.drag = !!move['drag'];
}
return moveOptions;
};

/**
* Parse the user-specified zoom options, using reasonable defaults where
* behaviour is unspecified. See zoom documentation:
Expand Down
35 changes: 1 addition & 34 deletions core/workspace_dragger.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,6 @@ Blockly.WorkspaceDragger = function(workspace) {
*/
this.workspace_ = workspace;

/**
* The workspace's metrics object at the beginning of the drag. Contains size
* and position metrics of a workspace.
* Coordinate system: pixel coordinates.
* @type {!Object}
* @private
*/
this.startDragMetrics_ = workspace.getMetrics();

/**
* The scroll position of the workspace at the beginning of the drag.
* Coordinate system: pixel coordinates.
Expand Down Expand Up @@ -102,30 +93,6 @@ Blockly.WorkspaceDragger.prototype.endDrag = function(currentDragDeltaXY) {
* @package
*/
Blockly.WorkspaceDragger.prototype.drag = function(currentDragDeltaXY) {
var metrics = this.startDragMetrics_;
var newXY = goog.math.Coordinate.sum(this.startScrollXY_, currentDragDeltaXY);

// Bound the new XY based on workspace bounds.
var x = Math.min(newXY.x, -metrics.contentLeft);
var y = Math.min(newXY.y, -metrics.contentTop);
x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
metrics.contentWidth);
y = Math.max(y, metrics.viewHeight - metrics.contentTop -
metrics.contentHeight);

x = -x - metrics.contentLeft;
y = -y - metrics.contentTop;

this.updateScroll_(x, y);
};

/**
* Move the scrollbars to drag the workspace.
* x and y are in pixels.
* @param {number} x The new x position to move the scrollbar to.
* @param {number} y The new y position to move the scrollbar to.
* @private
*/
Blockly.WorkspaceDragger.prototype.updateScroll_ = function(x, y) {
this.workspace_.scrollbar.set(x, y);
this.workspace_.scroll(newXY.x, newXY.y);
};
Loading

0 comments on commit 9dec2da

Please sign in to comment.