mirror of
https://github.com/google/blockly.git
synced 2026-01-08 17:40:09 +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 {Svg} from './utils/svg.js';
|
||||||
import type * as toolbox from './utils/toolbox.js';
|
import type * as toolbox from './utils/toolbox.js';
|
||||||
import type {WorkspaceSvg} from './workspace_svg.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.
|
* 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. */
|
/** The horizontal margin around the text in the button. */
|
||||||
static TEXT_MARGIN_X = 5;
|
static TEXT_MARGIN_X = 5;
|
||||||
|
|
||||||
@@ -41,7 +46,8 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
|||||||
private readonly cssClass: string | null;
|
private readonly cssClass: string | null;
|
||||||
|
|
||||||
/** Mouse up event data. */
|
/** Mouse up event data. */
|
||||||
private onMouseUpWrapper: browserEvents.Data | null = null;
|
private onMouseDownWrapper: browserEvents.Data;
|
||||||
|
private onMouseUpWrapper: browserEvents.Data;
|
||||||
info: toolbox.ButtonOrLabelInfo;
|
info: toolbox.ButtonOrLabelInfo;
|
||||||
|
|
||||||
/** The width of the button's rect. */
|
/** The width of the button's rect. */
|
||||||
@@ -51,7 +57,7 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
|||||||
height = 0;
|
height = 0;
|
||||||
|
|
||||||
/** The root SVG group for the button or label. */
|
/** 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. */
|
/** The SVG element with the text of the label or button. */
|
||||||
private svgText: SVGTextElement | null = null;
|
private svgText: SVGTextElement | null = null;
|
||||||
@@ -92,14 +98,6 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
|||||||
|
|
||||||
/** The JSON specifying the label / button. */
|
/** The JSON specifying the label / button. */
|
||||||
this.info = json;
|
this.info = json;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the button elements.
|
|
||||||
*
|
|
||||||
* @returns The button's SVG group.
|
|
||||||
*/
|
|
||||||
createDom(): SVGElement {
|
|
||||||
let cssClass = this.isFlyoutLabel
|
let cssClass = this.isFlyoutLabel
|
||||||
? 'blocklyFlyoutLabel'
|
? 'blocklyFlyoutLabel'
|
||||||
: 'blocklyFlyoutButton';
|
: 'blocklyFlyoutButton';
|
||||||
@@ -198,15 +196,24 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
|||||||
|
|
||||||
this.updateTransform();
|
this.updateTransform();
|
||||||
|
|
||||||
// AnyDuringMigration because: Argument of type 'SVGGElement | null' is not
|
this.onMouseDownWrapper = browserEvents.conditionalBind(
|
||||||
// assignable to parameter of type 'EventTarget'.
|
this.svgGroup,
|
||||||
|
'pointerdown',
|
||||||
|
this,
|
||||||
|
this.onMouseDown,
|
||||||
|
);
|
||||||
this.onMouseUpWrapper = browserEvents.conditionalBind(
|
this.onMouseUpWrapper = browserEvents.conditionalBind(
|
||||||
this.svgGroup as AnyDuringMigration,
|
this.svgGroup,
|
||||||
'pointerup',
|
'pointerup',
|
||||||
this,
|
this,
|
||||||
this.onMouseUp,
|
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. */
|
/** Correctly position the flyout button and make it visible. */
|
||||||
@@ -235,6 +242,17 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
|||||||
this.updateTransform();
|
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. */
|
/** @returns Whether or not the button is a label. */
|
||||||
isLabel(): boolean {
|
isLabel(): boolean {
|
||||||
return this.isFlyoutLabel;
|
return this.isFlyoutLabel;
|
||||||
@@ -250,6 +268,21 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
|||||||
return this.position;
|
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. */
|
/** @returns Text of the button. */
|
||||||
getButtonText(): string {
|
getButtonText(): string {
|
||||||
return this.text;
|
return this.text;
|
||||||
@@ -275,9 +308,8 @@ export class FlyoutButton implements IASTNodeLocationSvg {
|
|||||||
|
|
||||||
/** Dispose of this button. */
|
/** Dispose of this button. */
|
||||||
dispose() {
|
dispose() {
|
||||||
if (this.onMouseUpWrapper) {
|
browserEvents.unbind(this.onMouseDownWrapper);
|
||||||
browserEvents.unbind(this.onMouseUpWrapper);
|
browserEvents.unbind(this.onMouseUpWrapper);
|
||||||
}
|
|
||||||
if (this.svgGroup) {
|
if (this.svgGroup) {
|
||||||
dom.removeNode(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. */
|
/** 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