mirror of
https://github.com/google/blockly.git
synced 2026-01-08 09:30:06 +01:00
* Add annotations to files under core/events * Add annotations to files under core/interfaces * Add annotations to files under core/keyboard_nav * Add annotations to files under core/renderers * Add annotations to files under core/serialization * Add annotations to files under core/theme * Add annotations to files under core/toolbox * Add annotations to files under core/utils * Add annotations to files under core
182 lines
6.2 KiB
JavaScript
182 lines
6.2 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2021 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Utilities for bumping objects back into worksapce bounds.
|
|
* @author fenichel@google.com (Rachel Fenichel)
|
|
*/
|
|
'use strict';
|
|
|
|
/**
|
|
* Utilities for bumping objects back into worksapce bounds.
|
|
* @namespace Blockly.bumpObjects
|
|
*/
|
|
goog.module('Blockly.bumpObjects');
|
|
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const Abstract = goog.requireType('Blockly.Events.Abstract');
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const IBoundedElement = goog.requireType('Blockly.IBoundedElement');
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const MetricsManager = goog.requireType('Blockly.MetricsManager');
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const ViewportChange = goog.requireType('Blockly.Events.ViewportChange');
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const WorkspaceCommentSvg = goog.requireType('Blockly.WorkspaceCommentSvg');
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
|
|
const eventUtils = goog.require('Blockly.Events.utils');
|
|
const mathUtils = goog.require('Blockly.utils.math');
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
|
|
|
|
|
/**
|
|
* Bumps the given object that has passed out of bounds.
|
|
* @param {!WorkspaceSvg} workspace The workspace containing the object.
|
|
* @param {!MetricsManager.ContainerRegion} scrollMetrics Scroll metrics
|
|
* in workspace coordinates.
|
|
* @param {!IBoundedElement} object The object to bump.
|
|
* @return {boolean} True if block was bumped.
|
|
* @alias Blockly.bumpObjects.bumpIntoBounds
|
|
*/
|
|
const bumpObjectIntoBounds = function(workspace, scrollMetrics, object) {
|
|
// Compute new top/left position for object.
|
|
const objectMetrics = object.getBoundingRectangle();
|
|
const height = objectMetrics.bottom - objectMetrics.top;
|
|
const width = objectMetrics.right - objectMetrics.left;
|
|
|
|
const topClamp = scrollMetrics.top;
|
|
const scrollMetricsBottom = scrollMetrics.top + scrollMetrics.height;
|
|
const bottomClamp = scrollMetricsBottom - height;
|
|
// If the object is taller than the workspace we want to
|
|
// top-align the block
|
|
const newYPosition =
|
|
mathUtils.clamp(topClamp, objectMetrics.top, bottomClamp);
|
|
const deltaY = newYPosition - objectMetrics.top;
|
|
|
|
// Note: Even in RTL mode the "anchor" of the object is the
|
|
// top-left corner of the object.
|
|
let leftClamp = scrollMetrics.left;
|
|
const scrollMetricsRight = scrollMetrics.left + scrollMetrics.width;
|
|
let rightClamp = scrollMetricsRight - width;
|
|
if (workspace.RTL) {
|
|
// If the object is wider than the workspace and we're in RTL
|
|
// mode we want to right-align the block, which means setting
|
|
// the left clamp to match.
|
|
leftClamp = Math.min(rightClamp, leftClamp);
|
|
} else {
|
|
// If the object is wider than the workspace and we're in LTR
|
|
// mode we want to left-align the block, which means setting
|
|
// the right clamp to match.
|
|
rightClamp = Math.max(leftClamp, rightClamp);
|
|
}
|
|
const newXPosition =
|
|
mathUtils.clamp(leftClamp, objectMetrics.left, rightClamp);
|
|
const deltaX = newXPosition - objectMetrics.left;
|
|
|
|
if (deltaX || deltaY) {
|
|
object.moveBy(deltaX, deltaY);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
exports.bumpIntoBounds = bumpObjectIntoBounds;
|
|
|
|
/**
|
|
* Creates a handler for bumping objects when they cross fixed bounds.
|
|
* @param {!WorkspaceSvg} workspace The workspace to handle.
|
|
* @return {function(Abstract)} The event handler.
|
|
* @alias Blockly.bumpObjects.bumpIntoBoundsHandler
|
|
*/
|
|
const bumpIntoBoundsHandler = function(workspace) {
|
|
return function(e) {
|
|
const metricsManager = workspace.getMetricsManager();
|
|
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
|
|
return;
|
|
}
|
|
|
|
if (eventUtils.BUMP_EVENTS.indexOf(e.type) !== -1) {
|
|
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
|
|
|
|
// Triggered by move/create event
|
|
const object = extractObjectFromEvent(workspace, e);
|
|
if (!object) {
|
|
return;
|
|
}
|
|
// Handle undo.
|
|
const oldGroup = eventUtils.getGroup();
|
|
eventUtils.setGroup(e.group);
|
|
|
|
const wasBumped = bumpObjectIntoBounds(
|
|
workspace, scrollMetricsInWsCoords,
|
|
/** @type {!IBoundedElement} */ (object));
|
|
|
|
if (wasBumped && !e.group) {
|
|
console.warn(
|
|
'Moved object in bounds but there was no' +
|
|
' event group. This may break undo.');
|
|
}
|
|
if (oldGroup !== null) {
|
|
eventUtils.setGroup(oldGroup);
|
|
}
|
|
} else if (e.type === eventUtils.VIEWPORT_CHANGE) {
|
|
const viewportEvent = /** @type {!ViewportChange} */ (e);
|
|
if (viewportEvent.scale > viewportEvent.oldScale) {
|
|
bumpTopObjectsIntoBounds(workspace);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
exports.bumpIntoBoundsHandler = bumpIntoBoundsHandler;
|
|
|
|
/**
|
|
* Extracts the object from the given event.
|
|
* @param {!WorkspaceSvg} workspace The workspace the event originated
|
|
* from.
|
|
* @param {!eventUtils.BumpEvent} e An event containing an object.
|
|
* @return {?BlockSvg|?WorkspaceCommentSvg} The extracted
|
|
* object.
|
|
*/
|
|
const extractObjectFromEvent = function(workspace, e) {
|
|
let object = null;
|
|
switch (e.type) {
|
|
case eventUtils.BLOCK_CREATE:
|
|
case eventUtils.BLOCK_MOVE:
|
|
object = workspace.getBlockById(e.blockId);
|
|
if (object) {
|
|
object = object.getRootBlock();
|
|
}
|
|
break;
|
|
case eventUtils.COMMENT_CREATE:
|
|
case eventUtils.COMMENT_MOVE:
|
|
object = (
|
|
/** @type {?WorkspaceCommentSvg} */
|
|
(workspace.getCommentById(e.commentId)));
|
|
break;
|
|
}
|
|
return object;
|
|
};
|
|
|
|
/**
|
|
* Bumps the top objects in the given workspace into bounds.
|
|
* @param {!WorkspaceSvg} workspace The workspace.
|
|
* @alias Blockly.bumpObjects.bumpTopObjectsIntoBounds
|
|
*/
|
|
const bumpTopObjectsIntoBounds = function(workspace) {
|
|
const metricsManager = workspace.getMetricsManager();
|
|
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
|
|
return;
|
|
}
|
|
|
|
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
|
|
const topBlocks = workspace.getTopBoundedElements();
|
|
for (let i = 0, block; (block = topBlocks[i]); i++) {
|
|
bumpObjectIntoBounds(workspace, scrollMetricsInWsCoords, block);
|
|
}
|
|
};
|
|
exports.bumpTopObjectsIntoBounds = bumpTopObjectsIntoBounds;
|