Skip to content

Commit

Permalink
fix: render the procedure definition block like Scratch (#115)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gonfunko authored Aug 8, 2024
1 parent 936967b commit 2a543f5
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 1 deletion.
6 changes: 5 additions & 1 deletion blocks_vertical/procedures.js
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,11 @@ Blockly.Blocks["procedures_definition"] = {
name: "custom_block",
},
],
extensions: ["colours_more", "shape_hat", "procedure_def_contextmenu"],
extensions: [
"colours_more",
"shape_bowler_hat",
"procedure_def_contextmenu",
],
});
},
};
Expand Down
17 changes: 17 additions & 0 deletions blocks_vertical/vertical_extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ VerticalExtensions.SHAPE_STATEMENT = function () {
VerticalExtensions.SHAPE_HAT = function () {
this.setInputsInline(true);
this.setNextStatement(true, null);
this.hat = "cap";
};

/**
* Extension to make a block be shaped as a bowler hat block, with rounded
* corners on both sides and no indentation for statement blocks.
* @this {Blockly.Block}
* @readonly
*/
VerticalExtensions.SHAPE_BOWLER_HAT = function () {
this.setInputsInline(true);
this.setNextStatement(true, null);
this.hat = "bowler";
};

/**
Expand Down Expand Up @@ -259,6 +272,10 @@ VerticalExtensions.registerAll = function () {
VerticalExtensions.SHAPE_STATEMENT
);
Blockly.Extensions.register("shape_hat", VerticalExtensions.SHAPE_HAT);
Blockly.Extensions.register(
"shape_bowler_hat",
VerticalExtensions.SHAPE_BOWLER_HAT
);
Blockly.Extensions.register("shape_end", VerticalExtensions.SHAPE_END);

// Output shapes and types are related.
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import * as scratchBlocksUtils from "../core/scratch_blocks_utils.js";
import * as ScratchVariables from "./variables.js";
import "../core/css.js";
import "../core/field_vertical_separator.js";
import "./renderer/renderer.js";
import {
ContinuousToolbox,
ContinuousFlyout,
Expand Down Expand Up @@ -66,6 +67,8 @@ export { ScratchVariables };

export function inject(container, options) {
Object.assign(options, {
renderer: "scratch",
theme: "zelos",
plugins: {
toolbox: ScratchContinuousToolbox,
flyoutsVerticalToolbox: CheckableContinuousFlyout,
Expand Down
17 changes: 17 additions & 0 deletions src/renderer/bowler_hat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from "blockly/core";

export class BowlerHat extends Blockly.blockRendering.Hat {
constructor(constants) {
super(constants);
// Calculated dynamically by computeBounds_().
this.width = 0;
this.height = 20;
this.ascenderHeight = this.height;
}
}
51 changes: 51 additions & 0 deletions src/renderer/drawer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from "blockly/core";

export class Drawer extends Blockly.zelos.Drawer {
drawStatementInput_(row) {
if (this.info_.isBowlerHatBlock()) {
// Bowler hat blocks have straight sides with no C-shape/indentation for
// statement blocks.
this.drawRightSideRow_(row);
this.positionStatementInputConnection_(row);
} else {
super.drawStatementInput_(row);
}
}

drawRightSideRow_(row) {
if (
this.info_.isBowlerHatBlock() &&
Blockly.blockRendering.Types.isSpacer(row)
) {
// Multi-row bowler hat blocks are not supported, this may need
// adjustment to do so.
Blockly.blockRendering.Drawer.prototype.drawRightSideRow_.call(this, row);
} else {
super.drawRightSideRow_(row);
}
}

drawTop_() {
super.drawTop_();
// This is a horrible hack, but the superclass' implementation of drawTop_()
// provides no way to cleanly override a hat's path without copying and
// pasting the entire implementation here. We know that there will only be
// one hat on a block, and its path is a known constant, so we just find and
// replace it with the desired bowler hat path here.
// If https://github.com/google/blockly/issues/7292 is resolved, this should
// be revisited.
if (this.info_.isBowlerHatBlock()) {
const capHatPath = this.constants_.START_HAT.path;
const bowlerHatPath = `a20,20 0 0,1 20,-20 l ${
this.info_.width - 40
} 0 a20,20 0 0,1 20,20`;
this.outlinePath_ = this.outlinePath_.replace(capHatPath, bowlerHatPath);
}
}
}
89 changes: 89 additions & 0 deletions src/renderer/render_info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from "blockly/core";
import { BowlerHat } from "./bowler_hat.js";

export class RenderInfo extends Blockly.zelos.RenderInfo {
populateTopRow_() {
if (this.isBowlerHatBlock()) {
const bowlerHat = new BowlerHat(this.constants_);
this.topRow.elements.push(
new Blockly.blockRendering.SquareCorner(this.constants_)
);
this.topRow.elements.push(bowlerHat);
this.topRow.elements.push(
new Blockly.blockRendering.SquareCorner(this.constants_)
);
this.topRow.minHeight = 0;
this.topRow.capline = bowlerHat.ascenderHeight;
} else {
super.populateTopRow_();
}
}

populateBottomRow_() {
super.populateBottomRow_();
if (this.isBowlerHatBlock()) {
this.bottomRow.minHeight = this.constants_.MEDIUM_PADDING;
}
}

computeBounds_() {
super.computeBounds_();
if (this.isBowlerHatBlock()) {
// Resize the render info to the same width as the widest part of a
// bowler hat block.
const statementRow = this.rows.find((r) => r.hasStatement);
this.width =
statementRow.widthWithConnectedBlocks -
statementRow.elements.find((e) =>
Blockly.blockRendering.Types.isInput(e)
).width +
this.constants_.MEDIUM_PADDING;

// The bowler hat's width is the same as the block's width, so it can't
// be derived from the constants like a normal hat and has to be set here.
const hat = this.topRow.elements.find((e) =>
Blockly.blockRendering.Types.isHat(e)
);
hat.width = this.width;
this.topRow.measure(true);
}
}

getInRowSpacing_(prev, next) {
if (
this.isBowlerHatBlock() &&
((prev && Blockly.blockRendering.Types.isHat(prev)) ||
(next && Blockly.blockRendering.Types.isHat(next)))
) {
// Bowler hat rows have no spacing/gaps, just the hat.
return 0;
}

return super.getInRowSpacing_(prev, next);
}

getSpacerRowHeight_(prev, next) {
if (this.isBowlerHatBlock() && prev === this.topRow) {
return 0;
}

return super.getSpacerRowHeight_(prev, next);
}

getElemCenterline_(row, elem) {
if (this.isBowlerHatBlock() && Blockly.blockRendering.Types.isField(elem)) {
return row.yPos + elem.height;
}
return super.getElemCenterline_(row, elem);
}

isBowlerHatBlock() {
return this.block_.hat === "bowler";
}
}
21 changes: 21 additions & 0 deletions src/renderer/renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from "blockly/core";
import { Drawer } from "./drawer.js";
import { RenderInfo } from "./render_info.js";

export class ScratchRenderer extends Blockly.zelos.Renderer {
makeDrawer_(block, info) {
return new Drawer(block, info);
}

makeRenderInfo_(block) {
return new RenderInfo(this, block);
}
}

Blockly.blockRendering.register("scratch", ScratchRenderer);

0 comments on commit 2a543f5

Please sign in to comment.