mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
feat: Add inflaters for flyout labels and buttons. (#8593)
* feat: Add inflaters for flyout labels and buttons. * chore: Temporarily re-add createDom(). * chore: fix JSDoc. * chore: Add license. * chore: Add TSDoc.
This commit is contained in:
63
core/button_flyout_inflater.ts
Normal file
63
core/button_flyout_inflater.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @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 {FlyoutButton} from './flyout_button.js';
|
||||
import {ButtonOrLabelInfo} from './utils/toolbox.js';
|
||||
import * as registry from './registry.js';
|
||||
|
||||
/**
|
||||
* Class responsible for creating buttons for flyouts.
|
||||
*/
|
||||
export class ButtonFlyoutInflater implements IFlyoutInflater {
|
||||
/**
|
||||
* Inflates a flyout button from the given state and adds it to the flyout.
|
||||
*
|
||||
* @param state A JSON representation of a flyout button.
|
||||
* @param flyoutWorkspace The workspace to create the button on.
|
||||
* @returns A newly created FlyoutButton.
|
||||
*/
|
||||
load(state: Object, flyoutWorkspace: WorkspaceSvg): IBoundedElement {
|
||||
const button = new FlyoutButton(
|
||||
flyoutWorkspace,
|
||||
flyoutWorkspace.targetWorkspace!,
|
||||
state as ButtonOrLabelInfo,
|
||||
false,
|
||||
);
|
||||
button.show();
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of space that should follow this button.
|
||||
*
|
||||
* @param state A JSON representation of a flyout button.
|
||||
* @param defaultGap The default spacing for flyout items.
|
||||
* @returns The amount of space that should follow this button.
|
||||
*/
|
||||
gapForElement(state: Object, defaultGap: number): number {
|
||||
return defaultGap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the given button.
|
||||
*
|
||||
* @param element The flyout button to dispose of.
|
||||
*/
|
||||
disposeElement(element: IBoundedElement): void {
|
||||
if (element instanceof FlyoutButton) {
|
||||
element.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.FLYOUT_INFLATER,
|
||||
'button',
|
||||
ButtonFlyoutInflater,
|
||||
);
|
||||
@@ -20,12 +20,17 @@ import * as style from './utils/style.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import type * as toolbox from './utils/toolbox.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
import type {IASTNodeLocationSvg} from './blockly.js';
|
||||
import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js';
|
||||
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
|
||||
import type {IRenderedElement} from './interfaces/i_rendered_element.js';
|
||||
import {Rect} from './utils/rect.js';
|
||||
|
||||
/**
|
||||
* Class for a button or label in the flyout.
|
||||
*/
|
||||
export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
export class FlyoutButton
|
||||
implements IASTNodeLocationSvg, IBoundedElement, IRenderedElement
|
||||
{
|
||||
/** The horizontal margin around the text in the button. */
|
||||
static TEXT_MARGIN_X = 5;
|
||||
|
||||
@@ -41,7 +46,8 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
private readonly cssClass: string | null;
|
||||
|
||||
/** Mouse up event data. */
|
||||
private onMouseUpWrapper: browserEvents.Data | null = null;
|
||||
private onMouseDownWrapper: browserEvents.Data;
|
||||
private onMouseUpWrapper: browserEvents.Data;
|
||||
info: toolbox.ButtonOrLabelInfo;
|
||||
|
||||
/** The width of the button's rect. */
|
||||
@@ -51,7 +57,7 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
height = 0;
|
||||
|
||||
/** The root SVG group for the button or label. */
|
||||
private svgGroup: SVGGElement | null = null;
|
||||
private svgGroup: SVGGElement;
|
||||
|
||||
/** The SVG element with the text of the label or button. */
|
||||
private svgText: SVGTextElement | null = null;
|
||||
@@ -92,14 +98,6 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
|
||||
/** The JSON specifying the label / button. */
|
||||
this.info = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the button elements.
|
||||
*
|
||||
* @returns The button's SVG group.
|
||||
*/
|
||||
createDom(): SVGElement {
|
||||
let cssClass = this.isFlyoutLabel
|
||||
? 'blocklyFlyoutLabel'
|
||||
: 'blocklyFlyoutButton';
|
||||
@@ -198,15 +196,24 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
|
||||
this.updateTransform();
|
||||
|
||||
// AnyDuringMigration because: Argument of type 'SVGGElement | null' is not
|
||||
// assignable to parameter of type 'EventTarget'.
|
||||
this.onMouseDownWrapper = browserEvents.conditionalBind(
|
||||
this.svgGroup,
|
||||
'pointerdown',
|
||||
this,
|
||||
this.onMouseDown,
|
||||
);
|
||||
this.onMouseUpWrapper = browserEvents.conditionalBind(
|
||||
this.svgGroup as AnyDuringMigration,
|
||||
this.svgGroup,
|
||||
'pointerup',
|
||||
this,
|
||||
this.onMouseUp,
|
||||
);
|
||||
return this.svgGroup!;
|
||||
}
|
||||
|
||||
createDom(): SVGElement {
|
||||
// No-op, now handled in constructor. Will be removed in followup refactor
|
||||
// PR that updates the flyout classes to use inflaters.
|
||||
return this.svgGroup;
|
||||
}
|
||||
|
||||
/** Correctly position the flyout button and make it visible. */
|
||||
@@ -235,6 +242,17 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
this.updateTransform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the element by a relative offset.
|
||||
*
|
||||
* @param dx Horizontal offset in workspace units.
|
||||
* @param dy Vertical offset in workspace units.
|
||||
* @param _reason Why is this move happening? 'user', 'bump', 'snap'...
|
||||
*/
|
||||
moveBy(dx: number, dy: number, _reason?: string[]) {
|
||||
this.moveTo(this.position.x + dx, this.position.y + dy);
|
||||
}
|
||||
|
||||
/** @returns Whether or not the button is a label. */
|
||||
isLabel(): boolean {
|
||||
return this.isFlyoutLabel;
|
||||
@@ -250,6 +268,21 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the coordinates of a bounded element describing the dimensions of
|
||||
* the element. Coordinate system: workspace coordinates.
|
||||
*
|
||||
* @returns Object with coordinates of the bounded element.
|
||||
*/
|
||||
getBoundingRectangle() {
|
||||
return new Rect(
|
||||
this.position.y,
|
||||
this.position.y + this.height,
|
||||
this.position.x,
|
||||
this.position.x + this.width,
|
||||
);
|
||||
}
|
||||
|
||||
/** @returns Text of the button. */
|
||||
getButtonText(): string {
|
||||
return this.text;
|
||||
@@ -275,9 +308,8 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
|
||||
/** Dispose of this button. */
|
||||
dispose() {
|
||||
if (this.onMouseUpWrapper) {
|
||||
browserEvents.unbind(this.onMouseUpWrapper);
|
||||
}
|
||||
browserEvents.unbind(this.onMouseDownWrapper);
|
||||
browserEvents.unbind(this.onMouseUpWrapper);
|
||||
if (this.svgGroup) {
|
||||
dom.removeNode(this.svgGroup);
|
||||
}
|
||||
@@ -342,6 +374,21 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onMouseDown(e: PointerEvent) {
|
||||
const gesture = this.targetWorkspace.getGesture(e);
|
||||
const flyout = this.targetWorkspace.getFlyout();
|
||||
if (gesture && flyout) {
|
||||
gesture.handleFlyoutStart(e, flyout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The root SVG element of this rendered element.
|
||||
*/
|
||||
getSvgRoot() {
|
||||
return this.svgGroup;
|
||||
}
|
||||
}
|
||||
|
||||
/** CSS for buttons and labels. See css.js for use. */
|
||||
|
||||
59
core/label_flyout_inflater.ts
Normal file
59
core/label_flyout_inflater.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @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 {FlyoutButton} from './flyout_button.js';
|
||||
import {ButtonOrLabelInfo} from './utils/toolbox.js';
|
||||
import * as registry from './registry.js';
|
||||
|
||||
/**
|
||||
* Class responsible for creating labels for flyouts.
|
||||
*/
|
||||
export class LabelFlyoutInflater implements IFlyoutInflater {
|
||||
/**
|
||||
* Inflates a flyout label from the given state and adds it to the flyout.
|
||||
*
|
||||
* @param state A JSON representation of a flyout label.
|
||||
* @param flyoutWorkspace The workspace to create the label on.
|
||||
* @returns A FlyoutButton configured as a label.
|
||||
*/
|
||||
load(state: Object, flyoutWorkspace: WorkspaceSvg): IBoundedElement {
|
||||
const label = new FlyoutButton(
|
||||
flyoutWorkspace,
|
||||
flyoutWorkspace.targetWorkspace!,
|
||||
state as ButtonOrLabelInfo,
|
||||
true,
|
||||
);
|
||||
label.show();
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of space that should follow this label.
|
||||
*
|
||||
* @param state A JSON representation of a flyout label.
|
||||
* @param defaultGap The default spacing for flyout items.
|
||||
* @returns The amount of space that should follow this label.
|
||||
*/
|
||||
gapForElement(state: Object, defaultGap: number): number {
|
||||
return defaultGap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the given label.
|
||||
*
|
||||
* @param element The flyout label to dispose of.
|
||||
*/
|
||||
disposeElement(element: IBoundedElement): void {
|
||||
if (element instanceof FlyoutButton) {
|
||||
element.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(registry.Type.FLYOUT_INFLATER, 'label', LabelFlyoutInflater);
|
||||
Reference in New Issue
Block a user