Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: scroll-options fixes and compatibility with custom draggables #2358

Merged
merged 8 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ interface DynamicBlock extends Blockly.BlockSvg {
/**
* A type guard that checks if the given block fulfills the DynamicBlock
* interface.
*
* @param block
*/
export function blockIsDynamic(block: Blockly.BlockSvg): block is DynamicBlock {
return (
Expand Down
2 changes: 2 additions & 0 deletions plugins/block-dynamic-connection/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export const overrideOldBlockDefinitions = function (): void {
/**
* Finalizes connections when certain events (such as block deletion) are
* detected.
*
* @param e
*/
export function finalizeConnections(e: Blockly.Events.Abstract) {
if (e.type === Blockly.Events.BLOCK_DELETE) {
Expand Down
25 changes: 22 additions & 3 deletions plugins/scroll-options/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ scroll features.
This plugin adds two features related to scrolling:

- Users can scroll the workspace with the mousewheel while dragging a block
("wheel scroll").
- The workspace will automatically scroll when a block is dragged near the
edge of the workspace ("edge scroll").
or workspace comment ("wheel scroll").
- The workspace will automatically scroll when a block or workspace comment
is dragged near the edge of the workspace ("edge scroll").

Each of these options can be enabled or disabled independently, if you only want
one of these behaviors. The edge scrolling behavior can also be configured with
Expand Down Expand Up @@ -147,6 +147,25 @@ Then the final options used will include both `fastMouseSpeed: 5` and

You can call `ScrollBlockDragger.resetOptions()` to restore all default options.

## Usage with other plugins

This plugin ships with its own `MetricsManager` implementation. Other plugins
may ship with their own `MetricsManager` implementations as well. If you wish
to use multiple of these plugins together, you will need to create your own
implementation of `MetricsManager` that combines the necessary features of
both. This plugin simply requires that content metrics be cacheable so that
it can see content metrics from before a drag was started, even in the middle
of a drag. Your implementation must satisfy the `isCacheable` type guard in
`ScrollMetricsManager.ts`.

## Usage with custom draggables

Blockly supports adding custom objects that implement the `Blockly.IDraggable`
interface. Custom draggables are compatible with this plugin, but they will
not support the autoscrolling features by default. You can enable autoscrolling
features on them by also implementing the `AutoScrollable` interface on your
draggable objects.

## License

Apache 2.0
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
*/

import * as Blockly from 'blockly/core';
import {ScrollMetricsManager} from './ScrollMetricsManager';
import {isCacheable} from './ScrollMetricsManager';
import {getTranslation} from './utils';
import {ScrollBlockDragger} from './ScrollBlockDragger';

/* eslint-disable @typescript-eslint/naming-convention */

/**
* AutoScroll is used to scroll/pan the workspace automatically. For example,
* when a user drags a block near the edge of the workspace, it can begin
Expand All @@ -21,50 +23,34 @@ import {ScrollBlockDragger} from './ScrollBlockDragger';
* may get stuck in an infinite animation loop and crash the browser.
*/
export class AutoScroll {
/** Workspace to scroll. */
protected workspace_: Blockly.WorkspaceSvg;
protected dragger: ScrollBlockDragger;
/**
* Current active vector representing scroll velocity in pixels per
* millisecond in each direction.
*/
protected activeScrollVector_ = new Blockly.utils.Coordinate(0, 0);
/** ID of active requestAnimationFrame callback key. */
protected animationFrameId_ = 0;
/** Time in ms last animation frame was run. */
protected lastTime_: number = Date.now();
/**
* Whether the scroll animation should continue. If this is false, the next
* animation frame will not be requested.
*/
protected shouldAnimate_ = false;

/**
* Creates an AutoScroll instance for a specified workspace.
* @param {!Blockly.WorkspaceSvg} workspace Workspace to scroll.
* @param {!ScrollBlockDragger} dragger The dragger that's currently dragging.
*
* @param workspace Workspace to scroll.
* @param dragger The dragger that's currently dragging.
* @constructor
*/
constructor(workspace, dragger) {
/**
* Workspace to scroll.
* @protected {!Blockly.WorkspaceSvg}
*/
constructor(workspace: Blockly.WorkspaceSvg, dragger: ScrollBlockDragger) {
this.workspace_ = workspace;

/** @private {!ScrollBlockDragger} */
this.dragger = dragger;

/**
* Current active vector representing scroll velocity in pixels per
* millisecond in each direction.
* @protected {!Blockly.utils.Coordinate}
*/
this.activeScrollVector_ = new Blockly.utils.Coordinate(0, 0);

/**
* ID of active requestAnimationFrame callback key.
* @type {number}
* @protected
*/
this.animationFrameId_ = 0;

/**
* Time in ms last animation frame was run.
* @type {number}
* @protected
*/
this.lastTime_ = Date.now();

/**
* Whether the scroll animation should continue. If this is false, the next
* animation frame will not be requested.
* @type {boolean}
* @protected
*/
this.shouldAnimate_ = false;
}

/**
Expand All @@ -80,11 +66,11 @@ export class AutoScroll {
/**
* Ticks scrolling behavior and triggers another
* frame request.
* @param {number} now Current time in ms. This is usually passed
*
* @param now Current time in ms. This is usually passed
* automatically by `requestAnimationFrame`.
* @protected
*/
nextAnimationStep_(now) {
protected nextAnimationStep_(now: number) {
if (this.shouldAnimate_) {
const delta = now - this.lastTime_;
this.lastTime_ = now;
Expand All @@ -102,10 +88,10 @@ export class AutoScroll {

/**
* Perform scroll given time passed.
* @param {number} msPassed Number of ms since last scroll tick.
* @protected
*
* @param msPassed Number of ms since last scroll tick.
*/
scrollTick_(msPassed) {
protected scrollTick_(msPassed: number) {
const scrollDx = this.activeScrollVector_.x * msPassed;
const scrollDy = this.activeScrollVector_.y * msPassed;
this.scrollWorkspaceWithBlock(scrollDx, scrollDy);
Expand All @@ -114,18 +100,24 @@ export class AutoScroll {
/**
* Scrolls the workspace the given amount during a block drag.
* Also updates the dragger based on the amount actually scrolled.
* @param {number} scrollDx Amount to scroll in horizontal direction.
* @param {number} scrollDy Amount to scroll in vertical direction.
*
* @param scrollDx Amount to scroll in horizontal direction.
* @param scrollDy Amount to scroll in vertical direction.
*/
scrollWorkspaceWithBlock(scrollDx, scrollDy) {
scrollWorkspaceWithBlock(scrollDx: number, scrollDy: number) {
const oldLocation = getTranslation(this.workspace_);

// As we scroll, we shouldn't expand past the content area that existed
// before the block was picked up. Therefore, we use cached ContentMetrics
// so that the content area does not change as we scroll.
const metricsManager = /** @type {ScrollMetricsManager} */ (
this.workspace_.getMetricsManager()
);
const metricsManager = this.workspace_.getMetricsManager();

if (!isCacheable(metricsManager)) {
console.warn(
'MetricsManager must be able to cache metrics in order to use AutoScroll',
);
return;
}
metricsManager.useCachedContentMetrics = true;
const newX = this.workspace_.scrollX + scrollDx;
const newY = this.workspace_.scrollY + scrollDy;
Expand All @@ -149,10 +141,11 @@ export class AutoScroll {
/**
* Updates the scroll vector for the current autoscroll and begins the
* animation if needed.
* @param {!Blockly.utils.Coordinate} scrollVector New scroll velocity vector
*
* @param scrollVector New scroll velocity vector
* in pixels per ms.
*/
updateProperties(scrollVector) {
updateProperties(scrollVector: Blockly.utils.Coordinate) {
this.activeScrollVector_ = scrollVector;
this.shouldAnimate_ = true;

Expand Down
31 changes: 31 additions & 0 deletions plugins/scroll-options/src/AutoScrollable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from 'blockly/core';

/** A Draggable that can be autoscrolled. */
export interface AutoScrollable extends Blockly.IDraggable {
/**
* Returns the coordinates of a bounding box describing the dimensions of
* this draggable. This is necessary to detect when edge scrolling
* should activate.
*/
getBoundingRectangle: () => Blockly.utils.Rect;
}

/**
* Checks if the draggable implements the AutoScrollable interface.
*
* @param draggable
* @returns true if draggable is also AutoScrollable
*/
export function isAutoScrollable(
draggable: Blockly.IDraggable,
): draggable is AutoScrollable {
return (
typeof (draggable as AutoScrollable).getBoundingRectangle === 'function'
);
}
Loading
Loading