Files
blockly/core/bump_objects.ts
T
Christopher Allen b0475b0c68 chore: Fix whitespace (#6243)
* fix: Remove spurious blank lines

  Remove extraneous blank lines introduced by deletion of
  'use strict'; pragmas.

  Also fix the location of the goog.declareModuleId call in
  core/utils/array.ts.

* fix: Add missing double-blank-line before body of modules

  Our convention is to have two blank lines between the imports (or
  module ID, if there are no imports) and the beginning of the body
  of the module.  Enforce this.

* fix: one addition format error for PR #6243
2022-06-24 19:33:39 +01:00

189 lines
6.4 KiB
TypeScript

/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Utilities for bumping objects back into worksapce bounds.
*/
/**
* Utilities for bumping objects back into worksapce bounds.
* @namespace Blockly.bumpObjects
*/
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.bumpObjects');
/* eslint-disable-next-line no-unused-vars */
import {BlockSvg} from './block_svg.js';
/* eslint-disable-next-line no-unused-vars */
import {Abstract} from './events/events_abstract.js';
import {BlockCreate} from './events/events_block_create.js';
import {BlockMove} from './events/events_block_move.js';
import {CommentCreate} from './events/events_comment_create.js';
import {CommentMove} from './events/events_comment_move.js';
/* eslint-disable-next-line no-unused-vars */
import {ViewportChange} from './events/events_viewport.js';
import * as eventUtils from './events/utils.js';
/* eslint-disable-next-line no-unused-vars */
import {IBoundedElement} from './interfaces/i_bounded_element.js';
import {ContainerRegion, MetricsManager} from './metrics_manager.js';
import * as mathUtils from './utils/math.js';
/* eslint-disable-next-line no-unused-vars */
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
/* eslint-disable-next-line no-unused-vars */
import {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.
* @return 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.
* @return The event handler.
* @alias Blockly.bumpObjects.bumpIntoBoundsHandler
*/
export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
(p1: Abstract) => AnyDuringMigration {
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.
* @return 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);
}
}