Skip to content

Commit 2a543f5

Browse files
authored
fix: render the procedure definition block like Scratch (#115)
* fix: render the procedure definition block like Scratch * chore: add comment about cleanup opportunity * refactor: specify the theme and renderer in inject() * refactor: don't misstype the width field in BowlerHat * chore: add warning about multi-row bowler hat blocks
1 parent 936967b commit 2a543f5

File tree

7 files changed

+203
-1
lines changed

7 files changed

+203
-1
lines changed

blocks_vertical/procedures.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,11 @@ Blockly.Blocks["procedures_definition"] = {
777777
name: "custom_block",
778778
},
779779
],
780-
extensions: ["colours_more", "shape_hat", "procedure_def_contextmenu"],
780+
extensions: [
781+
"colours_more",
782+
"shape_bowler_hat",
783+
"procedure_def_contextmenu",
784+
],
781785
});
782786
},
783787
};

blocks_vertical/vertical_extensions.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,19 @@ VerticalExtensions.SHAPE_STATEMENT = function () {
9696
VerticalExtensions.SHAPE_HAT = function () {
9797
this.setInputsInline(true);
9898
this.setNextStatement(true, null);
99+
this.hat = "cap";
100+
};
101+
102+
/**
103+
* Extension to make a block be shaped as a bowler hat block, with rounded
104+
* corners on both sides and no indentation for statement blocks.
105+
* @this {Blockly.Block}
106+
* @readonly
107+
*/
108+
VerticalExtensions.SHAPE_BOWLER_HAT = function () {
109+
this.setInputsInline(true);
110+
this.setNextStatement(true, null);
111+
this.hat = "bowler";
99112
};
100113

101114
/**
@@ -259,6 +272,10 @@ VerticalExtensions.registerAll = function () {
259272
VerticalExtensions.SHAPE_STATEMENT
260273
);
261274
Blockly.Extensions.register("shape_hat", VerticalExtensions.SHAPE_HAT);
275+
Blockly.Extensions.register(
276+
"shape_bowler_hat",
277+
VerticalExtensions.SHAPE_BOWLER_HAT
278+
);
262279
Blockly.Extensions.register("shape_end", VerticalExtensions.SHAPE_END);
263280

264281
// Output shapes and types are related.

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import * as scratchBlocksUtils from "../core/scratch_blocks_utils.js";
2626
import * as ScratchVariables from "./variables.js";
2727
import "../core/css.js";
2828
import "../core/field_vertical_separator.js";
29+
import "./renderer/renderer.js";
2930
import {
3031
ContinuousToolbox,
3132
ContinuousFlyout,
@@ -66,6 +67,8 @@ export { ScratchVariables };
6667

6768
export function inject(container, options) {
6869
Object.assign(options, {
70+
renderer: "scratch",
71+
theme: "zelos",
6972
plugins: {
7073
toolbox: ScratchContinuousToolbox,
7174
flyoutsVerticalToolbox: CheckableContinuousFlyout,

src/renderer/bowler_hat.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @license
3+
* Copyright 2024 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import * as Blockly from "blockly/core";
8+
9+
export class BowlerHat extends Blockly.blockRendering.Hat {
10+
constructor(constants) {
11+
super(constants);
12+
// Calculated dynamically by computeBounds_().
13+
this.width = 0;
14+
this.height = 20;
15+
this.ascenderHeight = this.height;
16+
}
17+
}

src/renderer/drawer.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* @license
3+
* Copyright 2024 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import * as Blockly from "blockly/core";
8+
9+
export class Drawer extends Blockly.zelos.Drawer {
10+
drawStatementInput_(row) {
11+
if (this.info_.isBowlerHatBlock()) {
12+
// Bowler hat blocks have straight sides with no C-shape/indentation for
13+
// statement blocks.
14+
this.drawRightSideRow_(row);
15+
this.positionStatementInputConnection_(row);
16+
} else {
17+
super.drawStatementInput_(row);
18+
}
19+
}
20+
21+
drawRightSideRow_(row) {
22+
if (
23+
this.info_.isBowlerHatBlock() &&
24+
Blockly.blockRendering.Types.isSpacer(row)
25+
) {
26+
// Multi-row bowler hat blocks are not supported, this may need
27+
// adjustment to do so.
28+
Blockly.blockRendering.Drawer.prototype.drawRightSideRow_.call(this, row);
29+
} else {
30+
super.drawRightSideRow_(row);
31+
}
32+
}
33+
34+
drawTop_() {
35+
super.drawTop_();
36+
// This is a horrible hack, but the superclass' implementation of drawTop_()
37+
// provides no way to cleanly override a hat's path without copying and
38+
// pasting the entire implementation here. We know that there will only be
39+
// one hat on a block, and its path is a known constant, so we just find and
40+
// replace it with the desired bowler hat path here.
41+
// If https://github.com/google/blockly/issues/7292 is resolved, this should
42+
// be revisited.
43+
if (this.info_.isBowlerHatBlock()) {
44+
const capHatPath = this.constants_.START_HAT.path;
45+
const bowlerHatPath = `a20,20 0 0,1 20,-20 l ${
46+
this.info_.width - 40
47+
} 0 a20,20 0 0,1 20,20`;
48+
this.outlinePath_ = this.outlinePath_.replace(capHatPath, bowlerHatPath);
49+
}
50+
}
51+
}

src/renderer/render_info.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @license
3+
* Copyright 2024 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import * as Blockly from "blockly/core";
8+
import { BowlerHat } from "./bowler_hat.js";
9+
10+
export class RenderInfo extends Blockly.zelos.RenderInfo {
11+
populateTopRow_() {
12+
if (this.isBowlerHatBlock()) {
13+
const bowlerHat = new BowlerHat(this.constants_);
14+
this.topRow.elements.push(
15+
new Blockly.blockRendering.SquareCorner(this.constants_)
16+
);
17+
this.topRow.elements.push(bowlerHat);
18+
this.topRow.elements.push(
19+
new Blockly.blockRendering.SquareCorner(this.constants_)
20+
);
21+
this.topRow.minHeight = 0;
22+
this.topRow.capline = bowlerHat.ascenderHeight;
23+
} else {
24+
super.populateTopRow_();
25+
}
26+
}
27+
28+
populateBottomRow_() {
29+
super.populateBottomRow_();
30+
if (this.isBowlerHatBlock()) {
31+
this.bottomRow.minHeight = this.constants_.MEDIUM_PADDING;
32+
}
33+
}
34+
35+
computeBounds_() {
36+
super.computeBounds_();
37+
if (this.isBowlerHatBlock()) {
38+
// Resize the render info to the same width as the widest part of a
39+
// bowler hat block.
40+
const statementRow = this.rows.find((r) => r.hasStatement);
41+
this.width =
42+
statementRow.widthWithConnectedBlocks -
43+
statementRow.elements.find((e) =>
44+
Blockly.blockRendering.Types.isInput(e)
45+
).width +
46+
this.constants_.MEDIUM_PADDING;
47+
48+
// The bowler hat's width is the same as the block's width, so it can't
49+
// be derived from the constants like a normal hat and has to be set here.
50+
const hat = this.topRow.elements.find((e) =>
51+
Blockly.blockRendering.Types.isHat(e)
52+
);
53+
hat.width = this.width;
54+
this.topRow.measure(true);
55+
}
56+
}
57+
58+
getInRowSpacing_(prev, next) {
59+
if (
60+
this.isBowlerHatBlock() &&
61+
((prev && Blockly.blockRendering.Types.isHat(prev)) ||
62+
(next && Blockly.blockRendering.Types.isHat(next)))
63+
) {
64+
// Bowler hat rows have no spacing/gaps, just the hat.
65+
return 0;
66+
}
67+
68+
return super.getInRowSpacing_(prev, next);
69+
}
70+
71+
getSpacerRowHeight_(prev, next) {
72+
if (this.isBowlerHatBlock() && prev === this.topRow) {
73+
return 0;
74+
}
75+
76+
return super.getSpacerRowHeight_(prev, next);
77+
}
78+
79+
getElemCenterline_(row, elem) {
80+
if (this.isBowlerHatBlock() && Blockly.blockRendering.Types.isField(elem)) {
81+
return row.yPos + elem.height;
82+
}
83+
return super.getElemCenterline_(row, elem);
84+
}
85+
86+
isBowlerHatBlock() {
87+
return this.block_.hat === "bowler";
88+
}
89+
}

src/renderer/renderer.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @license
3+
* Copyright 2024 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import * as Blockly from "blockly/core";
8+
import { Drawer } from "./drawer.js";
9+
import { RenderInfo } from "./render_info.js";
10+
11+
export class ScratchRenderer extends Blockly.zelos.Renderer {
12+
makeDrawer_(block, info) {
13+
return new Drawer(block, info);
14+
}
15+
16+
makeRenderInfo_(block) {
17+
return new RenderInfo(this, block);
18+
}
19+
}
20+
21+
Blockly.blockRendering.register("scratch", ScratchRenderer);

0 commit comments

Comments
 (0)