From bdc43bd0f74d42cc653d9566cd359af613bb6a63 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 27 Sep 2024 13:23:56 -0700 Subject: [PATCH] feat: Add support for inflating flyout separators. (#8592) * feat: Add support for inflating flyout separators. * chore: Add license. * chore: Add TSDoc. * refactor: Allow specifying an axis for flyout separators. --- core/flyout_separator.ts | 61 +++++++++++++++++++++++++++ core/separator_flyout_inflater.ts | 69 +++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 core/flyout_separator.ts create mode 100644 core/separator_flyout_inflater.ts diff --git a/core/flyout_separator.ts b/core/flyout_separator.ts new file mode 100644 index 000000000..733371007 --- /dev/null +++ b/core/flyout_separator.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {IBoundedElement} from './interfaces/i_bounded_element.js'; +import {Rect} from './utils/rect.js'; + +/** + * Representation of a gap between elements in a flyout. + */ +export class FlyoutSeparator implements IBoundedElement { + private x = 0; + private y = 0; + + /** + * Creates a new separator. + * + * @param gap The amount of space this separator should occupy. + * @param axis The axis along which this separator occupies space. + */ + constructor( + private gap: number, + private axis: SeparatorAxis, + ) {} + + /** + * Returns the bounding box of this separator. + * + * @returns The bounding box of this separator. + */ + getBoundingRectangle(): Rect { + switch (this.axis) { + case SeparatorAxis.X: + return new Rect(this.y, this.y, this.x, this.x + this.gap); + case SeparatorAxis.Y: + return new Rect(this.y, this.y + this.gap, this.x, this.x); + } + } + + /** + * Repositions this separator. + * + * @param dx The distance to move this separator on the X axis. + * @param dy The distance to move this separator on the Y axis. + * @param _reason The reason this move was initiated. + */ + moveBy(dx: number, dy: number, _reason?: string[]) { + this.x += dx; + this.y += dy; + } +} + +/** + * Representation of an axis along which a separator occupies space. + */ +export const enum SeparatorAxis { + X = 'x', + Y = 'y', +} diff --git a/core/separator_flyout_inflater.ts b/core/separator_flyout_inflater.ts new file mode 100644 index 000000000..5ed02aeb9 --- /dev/null +++ b/core/separator_flyout_inflater.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {IFlyoutInflater} from './interfaces/i_flyout_inflater.js'; +import type {IBoundedElement} from './interfaces/i_bounded_element.js'; +import type {WorkspaceSvg} from './workspace_svg.js'; +import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js'; +import type {SeparatorInfo} from './utils/toolbox.js'; +import * as registry from './registry.js'; + +/** + * Class responsible for creating separators for flyouts. + */ +export class SeparatorFlyoutInflater implements IFlyoutInflater { + /** + * Inflates a dummy flyout separator. + * + * The flyout automatically creates separators between every element with a + * size determined by calling gapForElement on the relevant inflater. + * Additionally, users can explicitly add separators in the flyout definition. + * When separators (implicitly or explicitly created) follow one another, the + * gap of the last one propagates backwards and flattens to one separator. + * This flattening is not additive; if there are initially separators of 2, 3, + * and 4 pixels, after normalization there will be one separator of 4 pixels. + * Therefore, this method returns a zero-width separator, which will be + * replaced by the one implicitly created by the flyout based on the value + * returned by gapForElement, which knows the default gap, unlike this method. + * + * @param _state A JSON representation of a flyout separator. + * @param flyoutWorkspace The workspace the separator belongs to. + * @returns A newly created FlyoutSeparator. + */ + load(_state: Object, flyoutWorkspace: WorkspaceSvg): IBoundedElement { + const flyoutAxis = flyoutWorkspace.targetWorkspace?.getFlyout() + ?.horizontalLayout + ? SeparatorAxis.X + : SeparatorAxis.Y; + return new FlyoutSeparator(0, flyoutAxis); + } + + /** + * Returns the size of the separator. See `load` for more details. + * + * @param state A JSON representation of a flyout separator. + * @param defaultGap The default spacing for flyout items. + * @returns The desired size of the separator. + */ + gapForElement(state: Object, defaultGap: number): number { + const separatorState = state as SeparatorInfo; + const newGap = parseInt(String(separatorState['gap'])); + return newGap ?? defaultGap; + } + + /** + * Disposes of the given separator. Intentional no-op. + * + * @param _element The flyout separator to dispose of. + */ + disposeElement(_element: IBoundedElement): void {} +} + +registry.register( + registry.Type.FLYOUT_INFLATER, + 'sep', + SeparatorFlyoutInflater, +);