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

feat: Add inflaters for flyout labels and buttons. #8593

Merged
merged 5 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
35 changes: 35 additions & 0 deletions core/button_flyout_inflater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type {IFlyoutInflater} from './interfaces/i_flyout_inflater.js';
gonfunko marked this conversation as resolved.
Show resolved Hide resolved
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';

export class ButtonFlyoutInflater implements IFlyoutInflater {
load(state: Object, flyoutWorkspace: WorkspaceSvg): IBoundedElement {
const button = new FlyoutButton(
flyoutWorkspace,
flyoutWorkspace.targetWorkspace!,
state as ButtonOrLabelInfo,
false,
);
button.show();
return button;
}

gapForElement(state: Object, defaultGap: number): number {
return defaultGap;
}

disposeElement(element: IBoundedElement): void {
if (element instanceof FlyoutButton) {
element.dispose();
}
}
}

registry.register(
registry.Type.FLYOUT_INFLATER,
'button',
ButtonFlyoutInflater,
);
85 changes: 66 additions & 19 deletions core/flyout_button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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. */
Expand All @@ -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;
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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. */
Expand Down
31 changes: 31 additions & 0 deletions core/label_flyout_inflater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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';

export class LabelFlyoutInflater implements IFlyoutInflater {
load(state: Object, flyoutWorkspace: WorkspaceSvg): IBoundedElement {
const label = new FlyoutButton(
flyoutWorkspace,
flyoutWorkspace.targetWorkspace!,
state as ButtonOrLabelInfo,
true,
);
label.show();
return label;
}

gapForElement(state: Object, defaultGap: number): number {
return defaultGap;
}

disposeElement(element: IBoundedElement): void {
if (element instanceof FlyoutButton) {
element.dispose();
}
}
}

registry.register(registry.Type.FLYOUT_INFLATER, 'label', LabelFlyoutInflater);