merge develop into rc/v11.0.0

This commit is contained in:
Beka Westberg
2024-02-02 22:14:46 +00:00
31 changed files with 1214 additions and 385 deletions

View File

@@ -53,8 +53,8 @@ export interface PuzzleTab {
type: number;
width: number;
height: number;
pathDown: string | ((p1: number) => string);
pathUp: string | ((p1: number) => string);
pathDown: string;
pathUp: string;
}
/**
@@ -100,6 +100,22 @@ export function isDynamicShape(shape: Shape): shape is DynamicShape {
return (shape as DynamicShape).isDynamic;
}
/** Returns whether the shape is a puzzle tab or not. */
export function isPuzzleTab(shape: Shape): shape is PuzzleTab {
return (
(shape as PuzzleTab).pathDown !== undefined &&
(shape as PuzzleTab).pathUp !== undefined
);
}
/** Returns whether the shape is a notch or not. */
export function isNotch(shape: Shape): shape is Notch {
return (
(shape as Notch).pathLeft !== undefined &&
(shape as Notch).pathRight !== undefined
);
}
/**
* An object that provides constants for rendering blocks.
*/

View File

@@ -18,9 +18,10 @@ import type {PreviousConnection} from '../measurables/previous_connection.js';
import type {Row} from '../measurables/row.js';
import {Types} from '../measurables/types.js';
import {isDynamicShape} from './constants.js';
import {isDynamicShape, isNotch, isPuzzleTab} from './constants.js';
import type {ConstantProvider, Notch, PuzzleTab} from './constants.js';
import type {RenderInfo} from './info.js';
import {ConnectionType} from '../../connection_type.js';
/**
* An object that draws a block based on the given rendering information.
@@ -59,6 +60,7 @@ export class Drawer {
draw() {
this.drawOutline_();
this.drawInternals_();
this.updateConnectionHighlights();
this.block_.pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);
if (this.info_.RTL) {
@@ -429,4 +431,87 @@ export class Drawer {
);
}
}
/**
* Updates the path object to reflect which connections on the block are
* highlighted.
*/
protected updateConnectionHighlights() {
for (const row of this.info_.rows) {
for (const elem of row.elements) {
if (!(elem instanceof Connection)) continue;
if (elem.highlighted) {
this.drawConnectionHighlightPath(elem);
} else {
this.block_.pathObject.removeConnectionHighlight?.(
elem.connectionModel,
);
}
}
}
}
/** Returns a path to highlight the given connection. */
drawConnectionHighlightPath(measurable: Connection) {
const conn = measurable.connectionModel;
let path = '';
if (
conn.type === ConnectionType.INPUT_VALUE ||
conn.type === ConnectionType.OUTPUT_VALUE
) {
path = this.getExpressionConnectionHighlightPath(measurable);
} else {
path = this.getStatementConnectionHighlightPath(measurable);
}
const block = conn.getSourceBlock();
block.pathObject.addConnectionHighlight?.(
conn,
path,
conn.getOffsetInBlock(),
block.RTL,
);
}
/**
* Returns a path to highlight the given conneciton, assuming it is an
* input or output connection.
*/
private getExpressionConnectionHighlightPath(connection: Connection): string {
let connPath = '';
if (isDynamicShape(connection.shape)) {
connPath = connection.shape.pathDown(connection.height);
} else if (isPuzzleTab(connection.shape)) {
connPath = connection.shape.pathDown;
}
// We are assuming that there is room for the tab offset above and below
// the tab.
const yLen = this.constants_.TAB_OFFSET_FROM_TOP;
return (
svgPaths.moveBy(0, -yLen) +
svgPaths.lineOnAxis('v', yLen) +
connPath +
svgPaths.lineOnAxis('v', yLen)
);
}
/**
* Returns a path to highlight the given conneciton, assuming it is a
* next or previous connection.
*/
private getStatementConnectionHighlightPath(connection: Connection): string {
if (!isNotch(connection.shape)) {
throw new Error('Statement connections should have notch shapes');
}
const xLen =
this.constants_.NOTCH_OFFSET_LEFT - this.constants_.CORNER_RADIUS;
return (
svgPaths.moveBy(-xLen, 0) +
svgPaths.lineOnAxis('h', xLen) +
connection.shape.pathLeft +
svgPaths.lineOnAxis('h', xLen)
);
}
}

View File

@@ -9,7 +9,8 @@
import type {BlockStyle} from '../../theme.js';
import type {BlockSvg} from '../../block_svg.js';
import type {ConstantProvider} from './constants.js';
import {RenderedConnection} from '../../rendered_connection.js';
import type {RenderedConnection} from '../../rendered_connection.js';
import type {Coordinate} from '../../utils/coordinate.js';
/**
* An interface for a block's path object.
@@ -121,21 +122,16 @@ export interface IPathObject {
*/
updateMovable(enabled: boolean): void;
/**
* Add or remove styling that shows that if the dragging block is dropped,
* this block will be replaced. If a shadow block, it will disappear.
* Otherwise it will bump.
*
* @param enable True if styling should be added.
*/
updateReplacementFade(enabled: boolean): void;
/** Adds the given path as a connection highlight for the given connection. */
addConnectionHighlight?(
connection: RenderedConnection,
connectionPath: string,
offset: Coordinate,
rtl: boolean,
): void;
/**
* Add or remove styling that shows that if the dragging block is dropped,
* this block will be connected to the input.
*
* @param conn The connection on the input to highlight.
* @param enable True if styling should be added.
* Removes any highlight associated with the given connection, if it exists.
*/
updateShapeForInputHighlight(conn: RenderedConnection, enable: boolean): void;
removeConnectionHighlight?(connection: RenderedConnection): void;
}

View File

@@ -37,6 +37,7 @@ import {ValueInput} from '../../inputs/value_input.js';
import type {ConstantProvider} from './constants.js';
import type {Renderer} from './renderer.js';
import {Connection} from '../measurables/connection.js';
/**
* An object containing all sizing information needed to draw this block.
@@ -143,8 +144,7 @@ export class RenderInfo {
}
/**
* Populate and return an object containing all sizing information needed to
* draw this block.
* Populate this object with all sizing information needed to draw the block.
*
* This measure pass does not propagate changes to the block (although fields
* may choose to rerender when getSize() is called). However, calling it
@@ -748,4 +748,21 @@ export class RenderInfo {
this.startY = this.topRow.capline;
this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
}
/** Returns the connection measurable associated with the given connection. */
getMeasureableForConnection(conn: RenderedConnection): Connection | null {
if (this.outputConnection?.connectionModel === conn) {
return this.outputConnection;
}
for (const row of this.rows) {
for (const elem of row.elements) {
if (elem instanceof Connection && elem.connectionModel === conn) {
return elem;
}
}
}
return null;
}
}

View File

@@ -8,7 +8,9 @@
import type {BlockSvg} from '../../block_svg.js';
import type {Connection} from '../../connection.js';
import {RenderedConnection} from '../../rendered_connection.js';
import type {BlockStyle} from '../../theme.js';
import {Coordinate} from '../../utils/coordinate.js';
import * as dom from '../../utils/dom.js';
import {Svg} from '../../utils/svg.js';
@@ -38,6 +40,12 @@ export class PathObject implements IPathObject {
constants: ConstantProvider;
style: BlockStyle;
/** Highlight paths associated with connections. */
private connectionHighlights = new WeakMap<RenderedConnection, SVGElement>();
/** Locations of connection highlights. */
private highlightOffsets = new WeakMap<RenderedConnection, Coordinate>();
/**
* @param root The root SVG element.
* @param style The style object to use for colouring.
@@ -256,4 +264,53 @@ export class PathObject implements IPathObject {
updateShapeForInputHighlight(_conn: Connection, _enable: boolean) {
// NOOP
}
/** Adds the given path as a connection highlight for the given connection. */
addConnectionHighlight(
connection: RenderedConnection,
connectionPath: string,
offset: Coordinate,
rtl: boolean,
) {
if (this.connectionHighlights.has(connection)) {
if (this.currentHighlightMatchesNew(connection, connectionPath, offset)) {
return;
}
this.removeConnectionHighlight(connection);
}
const highlight = dom.createSvgElement(
Svg.PATH,
{
'class': 'blocklyHighlightedConnectionPath',
'd': connectionPath,
'transform':
`translate(${offset.x}, ${offset.y})` + (rtl ? ' scale(-1 1)' : ''),
},
this.svgRoot,
);
this.connectionHighlights.set(connection, highlight);
}
private currentHighlightMatchesNew(
connection: RenderedConnection,
newPath: string,
newOffset: Coordinate,
): boolean {
const currPath = this.connectionHighlights
.get(connection)
?.getAttribute('d');
const currOffset = this.highlightOffsets.get(connection);
return currPath === newPath && Coordinate.equals(currOffset, newOffset);
}
/**
* Removes any highlight associated with the given connection, if it exists.
*/
removeConnectionHighlight(connection: RenderedConnection) {
const highlight = this.connectionHighlights.get(connection);
if (!highlight) return;
dom.removeNode(highlight);
this.connectionHighlights.delete(connection);
}
}

View File

@@ -26,6 +26,7 @@ import type {IPathObject} from './i_path_object.js';
import {RenderInfo} from './info.js';
import {MarkerSvg} from './marker_svg.js';
import {PathObject} from './path_object.js';
import * as deprecation from '../../utils/deprecation.js';
/**
* The base class for a block renderer.
@@ -231,12 +232,21 @@ export class Renderer implements IRegistrable {
* @param local The connection currently being dragged.
* @param topBlock The block currently being dragged.
* @returns The preview type to display.
*
* @deprecated v10 - This function is no longer respected. A custom
* IConnectionPreviewer may be able to fulfill the functionality.
*/
getConnectionPreviewMethod(
closest: RenderedConnection,
local: RenderedConnection,
topBlock: BlockSvg,
): PreviewType {
deprecation.warn(
'getConnectionPreviewMethod',
'v10',
'v12',
'an IConnectionPreviewer, if it fulfills your use case.',
);
if (
local.type === ConnectionType.OUTPUT_VALUE ||
local.type === ConnectionType.PREVIOUS_STATEMENT

View File

@@ -40,6 +40,7 @@ export class Drawer extends BaseDrawer {
override draw() {
this.drawOutline_();
this.drawInternals_();
this.updateConnectionHighlights();
const pathObject = this.block_.pathObject as PathObject;
pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);

View File

@@ -20,6 +20,7 @@ import {Types} from './types.js';
export class Connection extends Measurable {
shape: Shape;
isDynamicShape: boolean;
highlighted: boolean;
/**
* @param constants The rendering constants provider.
@@ -32,9 +33,11 @@ export class Connection extends Measurable {
) {
super(constants);
this.shape = this.constants_.shapeFor(connectionModel);
this.isDynamicShape = 'isDynamic' in this.shape && this.shape.isDynamic;
this.type |= Types.CONNECTION;
this.shape = this.constants_.shapeFor(connectionModel);
this.isDynamicShape = 'isDynamic' in this.shape && this.shape.isDynamic;
this.highlighted = connectionModel.isHighlighted();
}
}

View File

@@ -392,19 +392,20 @@ export class ConstantProvider extends BaseConstantProvider {
blockHeight > maxHeight ? blockHeight - maxHeight : 0;
const height = blockHeight > maxHeight ? maxHeight : blockHeight;
const radius = height / 2;
const sweep = right === up ? '0' : '1';
return (
svgPaths.arc(
'a',
'0 0,1',
'0 0,' + sweep,
radius,
svgPaths.point((up ? -1 : 1) * radius, (up ? -1 : 1) * radius),
svgPaths.point((right ? 1 : -1) * radius, (up ? -1 : 1) * radius),
) +
svgPaths.lineOnAxis('v', (right ? 1 : -1) * remainingHeight) +
svgPaths.lineOnAxis('v', (up ? -1 : 1) * remainingHeight) +
svgPaths.arc(
'a',
'0 0,1',
'0 0,' + sweep,
radius,
svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius),
svgPaths.point((right ? -1 : 1) * radius, (up ? -1 : 1) * radius),
)
);
}
@@ -465,19 +466,20 @@ export class ConstantProvider extends BaseConstantProvider {
*/
function makeMainPath(height: number, up: boolean, right: boolean): string {
const innerHeight = height - radius * 2;
const sweep = right === up ? '0' : '1';
return (
svgPaths.arc(
'a',
'0 0,1',
'0 0,' + sweep,
radius,
svgPaths.point((up ? -1 : 1) * radius, (up ? -1 : 1) * radius),
svgPaths.point((right ? 1 : -1) * radius, (up ? -1 : 1) * radius),
) +
svgPaths.lineOnAxis('v', (right ? 1 : -1) * innerHeight) +
svgPaths.lineOnAxis('v', (up ? -1 : 1) * innerHeight) +
svgPaths.arc(
'a',
'0 0,1',
'0 0,' + sweep,
radius,
svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius),
svgPaths.point((right ? -1 : 1) * radius, (up ? -1 : 1) * radius),
)
);
}

View File

@@ -7,10 +7,13 @@
// Former goog.module ID: Blockly.zelos.Drawer
import type {BlockSvg} from '../../block_svg.js';
import {ConnectionType} from '../../connection_type.js';
import * as svgPaths from '../../utils/svg_paths.js';
import type {BaseShape, DynamicShape, Notch} from '../common/constants.js';
import {Drawer as BaseDrawer} from '../common/drawer.js';
import {Connection} from '../measurables/connection.js';
import type {InlineInput} from '../measurables/inline_input.js';
import {OutputConnection} from '../measurables/output_connection.js';
import type {Row} from '../measurables/row.js';
import type {SpacerRow} from '../measurables/spacer_row.js';
import {Types} from '../measurables/types.js';
@@ -41,6 +44,7 @@ export class Drawer extends BaseDrawer {
pathObject.beginDrawing();
this.drawOutline_();
this.drawInternals_();
this.updateConnectionHighlights();
pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);
if (this.info_.RTL) {
@@ -179,21 +183,27 @@ export class Drawer extends BaseDrawer {
return;
}
const width = input.width - input.connectionWidth * 2;
const height = input.height;
const yPos = input.centerline - height / 2;
const yPos = input.centerline - input.height / 2;
const connectionRight = input.xPos + input.connectionWidth;
const outlinePath =
svgPaths.moveTo(connectionRight, yPos) +
svgPaths.lineOnAxis('h', width) +
(input.shape as DynamicShape).pathRightDown(input.height) +
svgPaths.lineOnAxis('h', -width) +
(input.shape as DynamicShape).pathUp(input.height) +
'z';
const path =
svgPaths.moveTo(connectionRight, yPos) + this.getInlineInputPath(input);
const pathObject = this.block_.pathObject as PathObject;
pathObject.setOutlinePath(inputName, outlinePath);
pathObject.setOutlinePath(inputName, path);
}
private getInlineInputPath(input: InlineInput) {
const width = input.width - input.connectionWidth * 2;
const height = input.height;
return (
svgPaths.lineOnAxis('h', width) +
(input.shape as DynamicShape).pathRightDown(height) +
svgPaths.lineOnAxis('h', -width) +
(input.shape as DynamicShape).pathUp(height) +
'z'
);
}
override drawStatementInput_(row: Row) {
@@ -225,4 +235,40 @@ export class Drawer extends BaseDrawer {
this.positionStatementInputConnection_(row);
}
/** Returns a path to highlight the given connection. */
drawConnectionHighlightPath(measurable: Connection) {
const conn = measurable.connectionModel;
if (
conn.type === ConnectionType.NEXT_STATEMENT ||
conn.type === ConnectionType.PREVIOUS_STATEMENT ||
(conn.type === ConnectionType.OUTPUT_VALUE && !measurable.isDynamicShape)
) {
super.drawConnectionHighlightPath(measurable);
return;
}
let path = '';
if (conn.type === ConnectionType.INPUT_VALUE) {
const input = measurable as InlineInput;
const xPos = input.connectionWidth;
const yPos = -input.height / 2;
path = svgPaths.moveTo(xPos, yPos) + this.getInlineInputPath(input);
} else {
// Dynamic output.
const output = measurable as OutputConnection;
const xPos = output.width;
const yPos = -output.height / 2;
path =
svgPaths.moveTo(xPos, yPos) +
(output.shape as DynamicShape).pathDown(output.height);
}
const block = conn.getSourceBlock();
block.pathObject.addConnectionHighlight?.(
conn,
path,
conn.getOffsetInBlock(),
block.RTL,
);
}
}

View File

@@ -7,7 +7,6 @@
// Former goog.module ID: Blockly.zelos.Renderer
import type {BlockSvg} from '../../block_svg.js';
import type {Connection} from '../../connection.js';
import {ConnectionType} from '../../connection_type.js';
import {InsertionMarkerManager} from '../../insertion_marker_manager.js';
import type {Marker} from '../../keyboard_nav/marker.js';
@@ -23,6 +22,7 @@ import {Drawer} from './drawer.js';
import {RenderInfo} from './info.js';
import {MarkerSvg} from './marker_svg.js';
import {PathObject} from './path_object.js';
import * as deprecation from '../../utils/deprecation.js';
/**
* The zelos renderer. This renderer emulates Scratch-style and MakeCode-style
@@ -109,18 +109,21 @@ export class Renderer extends BaseRenderer {
return this.constants_;
}
override shouldHighlightConnection(conn: Connection) {
return (
conn.type !== ConnectionType.INPUT_VALUE &&
conn.type !== ConnectionType.OUTPUT_VALUE
);
}
/**
* @deprecated v10 - This function is no longer respected. A custom
* IConnectionPreviewer may be able to fulfill the functionality.
*/
override getConnectionPreviewMethod(
closest: RenderedConnection,
local: RenderedConnection,
topBlock: BlockSvg,
) {
deprecation.warn(
'getConnectionPreviewMethod',
'v10',
'v12',
'an IConnectionPreviewer, if it fulfills your use case.',
);
if (local.type === ConnectionType.OUTPUT_VALUE) {
if (!closest.isConnected()) {
return InsertionMarkerManager.PREVIEW_TYPE.INPUT_OUTLINE;