Files
blockly/core/scrollbar_pair.ts
Aaron Dodson 57a5d0d49c refactor: Remove more uses of AnyDuringMigration (#6378)
* refactor: Remove uses of AnyDuringMigration from flyout_vertical.ts.

* refactor: Remove uses of AnyDuringMigration in flyout_horizontal.ts.

* refactor: Remove uses of AnyDuringMigration from zoom_controls.ts.

* refactor: Remove uses of AnyDuringMigration from comment.ts.

* refactor: Remove uses of AnyDuringMigration from dialog.ts.

* refactor: Remove uses of AnyDuringMigration from icon.ts.

* refactor: Remove uses of AnyDuringMigration from scrollbar_pair.ts.

* refactor: Remove uses of AnyDuringMigration from workspace_audio.ts.

* refactor: Remove uses of AnyDuringMigration from workspace_drag_surface_svg.ts.
2022-08-23 15:36:52 -07:00

322 lines
9.4 KiB
TypeScript

/**
* @license
* Copyright 2011 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Object representing a pair of scrollbars.
*
* @class
*/
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.ScrollbarPair');
import * as eventUtils from './events/utils.js';
import {Scrollbar} from './scrollbar.js';
import * as dom from './utils/dom.js';
import type {Metrics} from './utils/metrics.js';
import {Svg} from './utils/svg.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Class for a pair of scrollbars. Horizontal and vertical.
*
* @alias Blockly.ScrollbarPair
*/
export class ScrollbarPair {
hScroll: Scrollbar|null = null;
vScroll: Scrollbar|null = null;
corner_: SVGRectElement|null = null;
/** Previously recorded metrics from the workspace. */
private oldHostMetrics_: Metrics|null = null;
/**
* @param workspace Workspace to bind the scrollbars to.
* @param addHorizontal Whether to add a horizontal scrollbar.
* Defaults to true.
* @param addVertical Whether to add a vertical scrollbar. Defaults to true.
* @param opt_class A class to be applied to these scrollbars.
* @param opt_margin The margin to apply to these scrollbars.
*/
constructor(
private workspace: WorkspaceSvg, addHorizontal?: boolean,
addVertical?: boolean, opt_class?: string, opt_margin?: number) {
addHorizontal = addHorizontal === undefined ? true : addHorizontal;
addVertical = addVertical === undefined ? true : addVertical;
const isPair = addHorizontal && addVertical;
if (addHorizontal) {
this.hScroll =
new Scrollbar(workspace, true, isPair, opt_class, opt_margin);
}
if (addVertical) {
this.vScroll =
new Scrollbar(workspace, false, isPair, opt_class, opt_margin);
}
if (isPair) {
this.corner_ = dom.createSvgElement(Svg.RECT, {
'height': Scrollbar.scrollbarThickness,
'width': Scrollbar.scrollbarThickness,
'class': 'blocklyScrollbarBackground',
});
dom.insertAfter(this.corner_, workspace.getBubbleCanvas());
}
}
/**
* Dispose of this pair of scrollbars.
* Unlink from all DOM elements to prevent memory leaks.
*
* @suppress {checkTypes}
*/
dispose() {
dom.removeNode(this.corner_);
this.corner_ = null;
this.oldHostMetrics_ = null;
if (this.hScroll) {
this.hScroll.dispose();
this.hScroll = null;
}
if (this.vScroll) {
this.vScroll.dispose();
this.vScroll = null;
}
}
/**
* Recalculate both of the scrollbars' locations and lengths.
* Also reposition the corner rectangle.
*/
resize() {
// Look up the host metrics once, and use for both scrollbars.
const hostMetrics = this.workspace.getMetrics();
if (!hostMetrics) {
// Host element is likely not visible.
return;
}
// Only change the scrollbars if there has been a change in metrics.
let resizeH = false;
let resizeV = false;
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth ||
this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight ||
this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop ||
this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft) {
// The window has been resized or repositioned.
resizeH = true;
resizeV = true;
} else {
// Has the content been resized or moved?
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.scrollWidth !== hostMetrics.scrollWidth ||
this.oldHostMetrics_.viewLeft !== hostMetrics.viewLeft ||
this.oldHostMetrics_.scrollLeft !== hostMetrics.scrollLeft) {
resizeH = true;
}
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.scrollHeight !== hostMetrics.scrollHeight ||
this.oldHostMetrics_.viewTop !== hostMetrics.viewTop ||
this.oldHostMetrics_.scrollTop !== hostMetrics.scrollTop) {
resizeV = true;
}
}
if (resizeH || resizeV) {
try {
eventUtils.disable();
if (this.hScroll && resizeH) {
this.hScroll.resize(hostMetrics);
}
if (this.vScroll && resizeV) {
this.vScroll.resize(hostMetrics);
}
} finally {
eventUtils.enable();
}
this.workspace.maybeFireViewportChangeEvent();
}
if (this.hScroll && this.vScroll) {
// Reposition the corner square.
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth ||
this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft) {
this.corner_?.setAttribute('x', String(this.vScroll.position.x));
}
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight ||
this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop) {
this.corner_?.setAttribute('y', String(this.hScroll.position.y));
}
}
// Cache the current metrics to potentially short-cut the next resize event.
this.oldHostMetrics_ = hostMetrics;
}
/**
* Returns whether scrolling horizontally is enabled.
*
* @returns True if horizontal scroll is enabled.
*/
canScrollHorizontally(): boolean {
return !!this.hScroll;
}
/**
* Returns whether scrolling vertically is enabled.
*
* @returns True if vertical scroll is enabled.
*/
canScrollVertically(): boolean {
return !!this.vScroll;
}
/**
* Record the origin of the workspace that the scrollbar is in, in pixels
* relative to the injection div origin. This is for times when the scrollbar
* is used in an object whose origin isn't the same as the main workspace
* (e.g. in a flyout.)
*
* @param x The x coordinate of the scrollbar's origin, in CSS pixels.
* @param y The y coordinate of the scrollbar's origin, in CSS pixels.
* @internal
*/
setOrigin(x: number, y: number) {
if (this.hScroll) {
this.hScroll.setOrigin(x, y);
}
if (this.vScroll) {
this.vScroll.setOrigin(x, y);
}
}
/**
* Set the handles of both scrollbars.
*
* @param x The horizontal content displacement, relative to the view in
* pixels.
* @param y The vertical content displacement, relative to the view in pixels.
* @param updateMetrics Whether to update metrics on this set call.
* Defaults to true.
*/
set(x: number, y: number, updateMetrics: boolean) {
// This function is equivalent to:
// this.hScroll.set(x);
// this.vScroll.set(y);
// However, that calls setMetrics twice which causes a chain of
// getAttribute->setAttribute->getAttribute resulting in an extra layout
// pass. Combining them speeds up rendering.
if (this.hScroll) {
this.hScroll.set(x, false);
}
if (this.vScroll) {
this.vScroll.set(y, false);
}
if (updateMetrics || updateMetrics === undefined) {
// Update metrics.
const xyRatio: {x?: number, y?: number} = {};
if (this.hScroll) {
xyRatio.x = this.hScroll.getRatio_();
}
if (this.vScroll) {
xyRatio.y = this.vScroll.getRatio_();
}
this.workspace.setMetrics(xyRatio);
}
}
/**
* Set the handle of the horizontal scrollbar to be at a certain position in
* CSS pixels relative to its parents.
*
* @param x Horizontal scroll value.
*/
setX(x: number) {
if (this.hScroll) {
this.hScroll.set(x, true);
}
}
/**
* Set the handle of the vertical scrollbar to be at a certain position in
* CSS pixels relative to its parents.
*
* @param y Vertical scroll value.
*/
setY(y: number) {
if (this.vScroll) {
this.vScroll.set(y, true);
}
}
/**
* Set whether this scrollbar's container is visible.
*
* @param visible Whether the container is visible.
*/
setContainerVisible(visible: boolean) {
if (this.hScroll) {
this.hScroll.setContainerVisible(visible);
}
if (this.vScroll) {
this.vScroll.setContainerVisible(visible);
}
}
/**
* If any of the scrollbars are visible. Non-paired scrollbars may disappear
* when they aren't needed.
*
* @returns True if visible.
*/
isVisible(): boolean {
let isVisible = false;
if (this.hScroll) {
isVisible = this.hScroll.isVisible();
}
if (this.vScroll) {
isVisible = isVisible || this.vScroll.isVisible();
}
return isVisible;
}
/**
* Recalculates the scrollbars' locations within their path and length.
* This should be called when the contents of the workspace have changed.
*
* @param hostMetrics A data structure describing all the required dimensions,
* possibly fetched from the host object.
*/
resizeContent(hostMetrics: Metrics) {
if (this.hScroll) {
this.hScroll.resizeContentHorizontal(hostMetrics);
}
if (this.vScroll) {
this.vScroll.resizeContentVertical(hostMetrics);
}
}
/**
* Recalculates the scrollbars' locations on the screen and path length.
* This should be called when the layout or size of the window has changed.
*
* @param hostMetrics A data structure describing all the required dimensions,
* possibly fetched from the host object.
*/
resizeView(hostMetrics: Metrics) {
if (this.hScroll) {
this.hScroll.resizeViewHorizontal(hostMetrics);
}
if (this.vScroll) {
this.vScroll.resizeViewVertical(hostMetrics);
}
}
}