mirror of
https://github.com/google/blockly.git
synced 2025-12-16 06:10:12 +01:00
* chore: add linting for tsdoc * chore: don't require types on return * chore: remove redundant fileoverview from ts * chore: change return to returns and add some newlines * chore: remove license tag * chore: don't require params/return docs * chore: remove spurious struct tags * Revert "chore: change return to returns and add some newlines" This reverts commitd6d8656a45. * chore: don't auto-add param names * chore: disable require-param bc it breaks on this * return to returns and add line breaks * chore: configure additional jsdoc rules * chore: run format * Revert "chore: remove license tag" This reverts commit173455588a. * chore: allow license tag format * chore: only require jsdoc on exported items * chore: add missing jsdoc or silence where needed * chore: run format * chore: lint fixes
184 lines
6.1 KiB
TypeScript
184 lines
6.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2021 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Utilities for bumping objects back into worksapce bounds.
|
|
*
|
|
* @namespace Blockly.bumpObjects
|
|
*/
|
|
import * as goog from '../closure/goog/goog.js';
|
|
goog.declareModuleId('Blockly.bumpObjects');
|
|
|
|
import type {BlockSvg} from './block_svg.js';
|
|
import type {Abstract} from './events/events_abstract.js';
|
|
import type {BlockCreate} from './events/events_block_create.js';
|
|
import type {BlockMove} from './events/events_block_move.js';
|
|
import type {CommentCreate} from './events/events_comment_create.js';
|
|
import type {CommentMove} from './events/events_comment_move.js';
|
|
import type {ViewportChange} from './events/events_viewport.js';
|
|
import * as eventUtils from './events/utils.js';
|
|
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
|
|
import type {ContainerRegion} from './metrics_manager.js';
|
|
import * as mathUtils from './utils/math.js';
|
|
import type {WorkspaceCommentSvg} from './workspace_comment_svg.js';
|
|
import type {WorkspaceSvg} from './workspace_svg.js';
|
|
|
|
|
|
/**
|
|
* Bumps the given object that has passed out of bounds.
|
|
*
|
|
* @param workspace The workspace containing the object.
|
|
* @param scrollMetrics Scroll metrics
|
|
* in workspace coordinates.
|
|
* @param object The object to bump.
|
|
* @returns True if block was bumped.
|
|
* @alias Blockly.bumpObjects.bumpIntoBounds
|
|
*/
|
|
function bumpObjectIntoBounds(
|
|
workspace: WorkspaceSvg, scrollMetrics: ContainerRegion,
|
|
object: IBoundedElement): boolean {
|
|
// 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;
|
|
}
|
|
export const bumpIntoBounds = bumpObjectIntoBounds;
|
|
|
|
/**
|
|
* Creates a handler for bumping objects when they cross fixed bounds.
|
|
*
|
|
* @param workspace The workspace to handle.
|
|
* @returns The event handler.
|
|
* @alias Blockly.bumpObjects.bumpIntoBoundsHandler
|
|
*/
|
|
export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
|
|
(p1: Abstract) => void {
|
|
return (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 as eventUtils.BumpEvent);
|
|
if (!object) {
|
|
return;
|
|
}
|
|
// Handle undo.
|
|
const oldGroup = eventUtils.getGroup();
|
|
eventUtils.setGroup(e.group);
|
|
|
|
const wasBumped = bumpObjectIntoBounds(
|
|
workspace, scrollMetricsInWsCoords, (object as IBoundedElement));
|
|
|
|
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 = (e as ViewportChange);
|
|
if (viewportEvent.scale && viewportEvent.oldScale &&
|
|
viewportEvent.scale > viewportEvent.oldScale) {
|
|
bumpTopObjectsIntoBounds(workspace);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Extracts the object from the given event.
|
|
*
|
|
* @param workspace The workspace the event originated
|
|
* from.
|
|
* @param e An event containing an object.
|
|
* @returns The extracted
|
|
* object.
|
|
*/
|
|
function extractObjectFromEvent(
|
|
workspace: WorkspaceSvg, e: eventUtils.BumpEvent): BlockSvg|null|
|
|
WorkspaceCommentSvg {
|
|
let object = null;
|
|
switch (e.type) {
|
|
case eventUtils.BLOCK_CREATE:
|
|
case eventUtils.BLOCK_MOVE:
|
|
object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId);
|
|
if (object) {
|
|
object = object.getRootBlock();
|
|
}
|
|
break;
|
|
case eventUtils.COMMENT_CREATE:
|
|
case eventUtils.COMMENT_MOVE:
|
|
object = workspace.getCommentById(
|
|
(e as CommentCreate | CommentMove).commentId) as
|
|
WorkspaceCommentSvg |
|
|
null;
|
|
break;
|
|
}
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
* Bumps the top objects in the given workspace into bounds.
|
|
*
|
|
* @param workspace The workspace.
|
|
* @alias Blockly.bumpObjects.bumpTopObjectsIntoBounds
|
|
*/
|
|
export function bumpTopObjectsIntoBounds(workspace: WorkspaceSvg) {
|
|
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);
|
|
}
|
|
}
|