From fd7950420519324ad472e4773917b6b6322781cd Mon Sep 17 00:00:00 2001 From: jwklong <72522395+jwklong@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:14:51 +0100 Subject: [PATCH 1/5] notch spacing --- core/block_render_svg.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/core/block_render_svg.js b/core/block_render_svg.js index bc30c6b13d..341953282b 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -121,6 +121,12 @@ Blockly.BlockSvg.NOTCH_WIDTH = 8 * Blockly.BlockSvg.GRID_UNIT; */ Blockly.BlockSvg.NOTCH_HEIGHT = 2 * Blockly.BlockSvg.GRID_UNIT; +/** + * spacing of notch + * @const + */ +Blockly.BlockSvg.NOTCH_SPACING = 0; // 4 * Blockly.BlockSvg.GRID_UNIT; + /** * Rounded corner radius. * @const @@ -1043,6 +1049,7 @@ Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) { if (row.statementNotchAtBottom) { paddedHeight -= Blockly.BlockSvg.NOTCH_HEIGHT; } + paddedHeight += Blockly.BlockSvg.NOTCH_SPACING; } input.renderHeight = Math.max(input.renderHeight, paddedHeight); input.renderWidth = Math.max(input.renderWidth, paddedWidth); @@ -1602,7 +1609,7 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, fieldY += Blockly.BlockSvg.MIN_STATEMENT_INPUT_HEIGHT; this.renderFields_(input.fieldRow, fieldX, fieldY); // Move to the start of the notch. - cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH; + cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.NOTCH_SPACING; if (this.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE) { this.renderDefineBlock_(steps, inputRows, input, row, cursorY); @@ -1613,7 +1620,7 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, // Create statement connection. connectionX = this.RTL ? -cursorX : cursorX; - input.connection.setOffsetInBlock(connectionX, cursorY); + input.connection.setOffsetInBlock(connectionX, cursorY + Blockly.BlockSvg.NOTCH_SPACING); if (input.connection.isConnected()) { this.width = Math.max(this.width, inputRows.statementEdge + input.connection.targetBlock().getHeightWidth().width + (!this.isCollapsed() && this.inputList.find(v => v.type == Blockly.NEXT_STATEMENT) ? this.edgeShapeWidth_ : 0)); @@ -1711,9 +1718,9 @@ Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, cursorY) { var connectionX = Blockly.BlockSvg.NOTCH_WIDTH; if (this.edgeShape_ && (!this.previousConnection || !this.previousConnection.targetConnection || !this.getSurroundParent())) connectionX += this.edgeShapeWidth_; connectionX *= this.RTL ? -1 : 1; - this.nextConnection.setOffsetInBlock(connectionX, cursorY); + this.nextConnection.setOffsetInBlock(connectionX, cursorY + Blockly.BlockSvg.NOTCH_SPACING); // Include height of notch in block height. - this.height += Blockly.BlockSvg.NOTCH_HEIGHT; + this.height += Blockly.BlockSvg.NOTCH_HEIGHT + Blockly.BlockSvg.NOTCH_SPACING; } // Bottom horizontal line if (!this.edgeShape_ || !(!this.previousConnection || !this.previousConnection.targetConnection || !this.getSurroundParent())) { @@ -1832,7 +1839,7 @@ Blockly.BlockSvg.drawStatementInputTop_ = function(steps, cursorX, block, row) { if (customNotch) steps.push(customNotch.right); else steps.push(Blockly.BlockSvg.NOTCH_PATH_RIGHT); - steps.push('h', '-' + Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE); + steps.push('h', -Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE - Blockly.BlockSvg.NOTCH_SPACING); steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER); }; @@ -1850,7 +1857,7 @@ Blockly.BlockSvg.drawStatementInputTop_ = function(steps, cursorX, block, row) { Blockly.BlockSvg.drawStatementInputBottom_ = function(steps, rightEdge, row) { steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER); if (row.statementNotchAtBottom) { - steps.push('h ', Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE); + steps.push('h ', Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE + Blockly.BlockSvg.NOTCH_SPACING); // if we have a custom check that corresponds to a custom notch, use it const checkStatement = (row[0].connection.check_ || []).length === 1 ? row[0].connection.check_[0] : null; @@ -2594,4 +2601,4 @@ Blockly.BlockSvg.registerCustomShape = function(name, shapeInfo) { } Blockly.BlockSvg.CUSTOM_SHAPES.set(name, shapeInfo); -} \ No newline at end of file +} From cf8f80c5475883c53245bf1ff99266e429c45d85 Mon Sep 17 00:00:00 2001 From: jwklong Date: Fri, 16 Oct 2026 16:36:47 +0100 Subject: [PATCH 2/5] tweaks Signed-off-by: jwklong --- core/block_render_svg.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/block_render_svg.js b/core/block_render_svg.js index 341953282b..fda1f9dc35 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -1049,7 +1049,9 @@ Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) { if (row.statementNotchAtBottom) { paddedHeight -= Blockly.BlockSvg.NOTCH_HEIGHT; } - paddedHeight += Blockly.BlockSvg.NOTCH_SPACING; + if (input.connection.targetConnection) { + paddedHeight += Blockly.BlockSvg.NOTCH_SPACING * 2; + } } input.renderHeight = Math.max(input.renderHeight, paddedHeight); input.renderWidth = Math.max(input.renderWidth, paddedWidth); @@ -1720,7 +1722,8 @@ Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, cursorY) { connectionX *= this.RTL ? -1 : 1; this.nextConnection.setOffsetInBlock(connectionX, cursorY + Blockly.BlockSvg.NOTCH_SPACING); // Include height of notch in block height. - this.height += Blockly.BlockSvg.NOTCH_HEIGHT + Blockly.BlockSvg.NOTCH_SPACING; + this.height += Blockly.BlockSvg.NOTCH_HEIGHT; + if (this.nextConnection.targetConnection) this.height += Blockly.BlockSvg.NOTCH_SPACING; } // Bottom horizontal line if (!this.edgeShape_ || !(!this.previousConnection || !this.previousConnection.targetConnection || !this.getSurroundParent())) { From 3d04869bdfcb0b19e9070763d864da97bfd2e9a3 Mon Sep 17 00:00:00 2001 From: jwklong Date: Fri, 16 Oct 2026 17:38:54 +0100 Subject: [PATCH 3/5] notch height Signed-off-by: jwklong --- core/block_render_svg.js | 89 ++++++++-------------------------------- core/path_utils.js | 63 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 73 deletions(-) create mode 100644 core/path_utils.js diff --git a/core/block_render_svg.js b/core/block_render_svg.js index fda1f9dc35..d130e2468f 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -29,6 +29,7 @@ goog.provide('Blockly.BlockSvg.render'); goog.require('Blockly.BlockSvg'); goog.require('Blockly.scratchBlocksUtils'); goog.require('Blockly.utils'); +goog.require('Blockly.pathUtils'); const getSubprop = (x, prop) => { if (typeof x == "object") return x[prop]; @@ -125,7 +126,7 @@ Blockly.BlockSvg.NOTCH_HEIGHT = 2 * Blockly.BlockSvg.GRID_UNIT; * spacing of notch * @const */ -Blockly.BlockSvg.NOTCH_SPACING = 0; // 4 * Blockly.BlockSvg.GRID_UNIT; +Blockly.BlockSvg.NOTCH_SPACING = 0; //3 * Blockly.BlockSvg.GRID_UNIT; /** * Rounded corner radius. @@ -1515,9 +1516,10 @@ Blockly.BlockSvg.prototype.renderDrawTop_ = function(steps, rightEdge) { // if we have a custom check that corresponds to a custom notch, use it const checkStatement = (this.previousConnection.check_ || []).length === 1 ? this.previousConnection.check_[0] : null; + let notch = Blockly.BlockSvg.NOTCH_PATH_LEFT; const customNotch = Blockly.BlockSvg.CUSTOM_NOTCHES.get(checkStatement); - if (customNotch) steps.push(customNotch.left); - else steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT); + if (customNotch) notch = customNotch.left; + steps.push(Blockly.pathUtils.scale(notch, 1, Blockly.BlockSvg.NOTCH_HEIGHT / (2 * Blockly.BlockSvg.GRID_UNIT))); // Create previous block connection. let connectionX = Blockly.BlockSvg.NOTCH_WIDTH; @@ -1712,9 +1714,10 @@ Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, cursorY) { // if we have a custom check that corresponds to a custom notch, use it const checkStatement = (this.nextConnection.check_ || []).length === 1 ? this.nextConnection.check_[0] : null; + let notch = Blockly.BlockSvg.NOTCH_PATH_RIGHT; const customNotch = Blockly.BlockSvg.CUSTOM_NOTCHES.get(checkStatement); - if (customNotch) steps.push(customNotch.right); - else steps.push(Blockly.BlockSvg.NOTCH_PATH_RIGHT); + if (customNotch) notch = customNotch.right; + steps.push(Blockly.pathUtils.scale(notch, 1, Blockly.BlockSvg.NOTCH_HEIGHT / (2 * Blockly.BlockSvg.GRID_UNIT))); // Create next block connection. var connectionX = Blockly.BlockSvg.NOTCH_WIDTH; @@ -1838,9 +1841,10 @@ Blockly.BlockSvg.drawStatementInputTop_ = function(steps, cursorX, block, row) { // if we have a custom check that corresponds to a custom notch, use it const checkStatement = (row[0].connection.check_ || []).length === 1 ? row[0].connection.check_[0] : null; + let notch = Blockly.BlockSvg.NOTCH_PATH_RIGHT; const customNotch = Blockly.BlockSvg.CUSTOM_NOTCHES.get(checkStatement); - if (customNotch) steps.push(customNotch.right); - else steps.push(Blockly.BlockSvg.NOTCH_PATH_RIGHT); + if (customNotch) notch = customNotch.right; + steps.push(Blockly.pathUtils.scale(notch, 1, Blockly.BlockSvg.NOTCH_HEIGHT / (2 * Blockly.BlockSvg.GRID_UNIT))); steps.push('h', -Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE - Blockly.BlockSvg.NOTCH_SPACING); steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER); @@ -1864,9 +1868,10 @@ Blockly.BlockSvg.drawStatementInputBottom_ = function(steps, rightEdge, row) { // if we have a custom check that corresponds to a custom notch, use it const checkStatement = (row[0].connection.check_ || []).length === 1 ? row[0].connection.check_[0] : null; + let notch = Blockly.BlockSvg.NOTCH_PATH_LEFT; const customNotch = Blockly.BlockSvg.CUSTOM_NOTCHES.get(checkStatement); - if (customNotch) steps.push(customNotch.left); - else steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT); + if (customNotch) notch = customNotch.left; + steps.push(Blockly.pathUtils.scale(notch, 1, Blockly.BlockSvg.NOTCH_HEIGHT / (2 * Blockly.BlockSvg.GRID_UNIT))); } steps.push('H', rightEdge - Blockly.BlockSvg.CORNER_RADIUS); }; @@ -2430,67 +2435,6 @@ Blockly.BlockSvg.CUSTOM_SHAPES = new Map([ }] ]); -/* -= Custom Block Notch API =- */ -// utility functions -Blockly.BlockSvg.CUSTOM_NOTCH_UTIL = { - supportedCommands: { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7 }, - commandXpos: { m: [0], l: [0], h: [0], c: [0, 2, 4], s: [0, 2], q: [0, 2], t: [0], a: [5] }, - path2TokenList: (pathStr) => { - return pathStr.match(/[a-z]|-?\d*\.?\d+(?:[eE][-+]?\d+)?/g); - }, - validateSVGPath: (pathStr) => { - const util = Blockly.BlockSvg.CUSTOM_NOTCH_UTIL; - const tokens = util.path2TokenList(pathStr); - let i = 0; - while (i < tokens.length) { - const cmd = tokens[i++]; - const expected = util.supportedCommands[cmd]; - if (expected === undefined) throw new Error(`Unsupported or invalid command '${cmd}'`); - while (i + expected <= tokens.length && !/^[a-z]$/.test(tokens[i])) { - for (let j = 0; j < expected; j++) { - if (!/^[-+]?\d*\.?\d+(e[-+]?\d+)?$/i.test(tokens[i + j])) { - throw new Error(`Invalid number '${tokens[i + j]}' in '${cmd}' command`); - } - } - i += expected; - } - } - return true; - }, - flipPathX: (pathStr) => { - const util = Blockly.BlockSvg.CUSTOM_NOTCH_UTIL; - const tokens = util.path2TokenList(pathStr); - let i = 0, result = []; - while (i < tokens.length) { - const cmd = tokens[i++]; - result.push(cmd); - const expected = util.supportedCommands[cmd]; - while (i + expected <= tokens.length && !/^[a-z]$/.test(tokens[i])) { - const group = []; - if (cmd === 'a') { - // Arc: rx ry x-axis-rotation large-arc-flag sweep-flag dx dy - group.push( - parseFloat(tokens[i]), parseFloat(tokens[i + 1]), - parseFloat(tokens[i + 2]), parseInt(tokens[i + 3]), - 1 - parseInt(tokens[i + 4]), - -parseFloat(tokens[i + 5]), parseFloat(tokens[i + 6]) - ); - } else { - const xIndexes = util.commandXpos[cmd] || []; - for (let j = 0; j < expected; j++) { - let val = parseFloat(tokens[i + j]); - if (xIndexes.includes(j)) val = -val; - group.push(val); - } - } - result.push(...group); - i += expected; - } - } - return result.join(' '); - } -}; - // Stores all user-defined custom shapes Blockly.BlockSvg.CUSTOM_NOTCHES = new Map([]); @@ -2510,9 +2454,8 @@ Blockly.BlockSvg.registerCustomNotch = function(name, path) { } try { - const util = Blockly.BlockSvg.CUSTOM_NOTCH_UTIL; - if (util.validateSVGPath(path)) Blockly.BlockSvg.CUSTOM_NOTCHES.set(String(name), { - left: path, right: util.flipPathX(path) + Blockly.BlockSvg.CUSTOM_NOTCHES.set(String(name), { + left: path, right: Blockly.pathUtils.scale(path, -1, 1) }); } catch (err) { // svg path was probably Invalid diff --git a/core/path_utils.js b/core/path_utils.js new file mode 100644 index 0000000000..ba13cc2c4e --- /dev/null +++ b/core/path_utils.js @@ -0,0 +1,63 @@ +goog.provide('Blockly.pathUtils'); + +Blockly.pathUtils.supportedCommands = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7 }; +Blockly.pathUtils.commandXpos = { m: [1], l: [1], h: [1], v: [], c: [1, 3, 5], s: [1, 3], q: [1, 3], t: [1], a: [1, 6] }; +Blockly.pathUtils.commandYpos = { m: [2], l: [2], h: [], v: [1], c: [2, 4, 6], s: [2, 4], q: [2, 4], t: [2], a: [2, 7] }; + +/** + * @typedef {[string, ...number[]]} PathTokens + */ + +/** + * @param {string} path + * @returns {PathTokens} + */ +Blockly.pathUtils.tokenize = function (path) { + const match = path.match(/[a-z]|-?\d*\.?\d+(?:[eE][-+]?\d+)?/g); + const output = []; + for (const v of match) { + if (isNaN(v)) { // letter + output.push([v]); + } else { + output[output.length - 1].push(Number(v)); + } + } + return output; +} + +/** + * @param {PathTokens} tokens + * @returns {string} + */ +Blockly.pathUtils.detokenize = function (tokens) { + return tokens.map(token => token.join(" ")).join(" "); +} + +/** + * @param {string} path + * @param {number} x + * @param {number} y + * @returns {string} + */ +Blockly.pathUtils.scale = function (path, x, y) { + const tokens = Blockly.pathUtils.tokenize(path); + + for (const token of tokens) { + const cmd = token[0].toLowerCase(); + + for (const xIndex of Blockly.pathUtils.commandXpos[cmd]) { + token[xIndex] *= x; + } + for (const yIndex of Blockly.pathUtils.commandYpos[cmd]) { + token[yIndex] *= y; + } + + if (cmd === "a") { // special + token[5] ^= (Math.sign(x) < 0) ^ (Math.sign(y) < 0); // sweep + + // i really really do not want to implement x-axis-rotation + } + } + + return Blockly.pathUtils.detokenize(tokens); +} \ No newline at end of file From 21a0ce7b5f54d726566de280606319439da1de6d Mon Sep 17 00:00:00 2001 From: jwklong Date: Fri, 16 Oct 2026 22:58:33 +0100 Subject: [PATCH 4/5] fix corner radius stuff as well Signed-off-by: jwklong --- core/block_render_svg.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/block_render_svg.js b/core/block_render_svg.js index d130e2468f..607333ed6f 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -132,7 +132,7 @@ Blockly.BlockSvg.NOTCH_SPACING = 0; //3 * Blockly.BlockSvg.GRID_UNIT; * Rounded corner radius. * @const */ -Blockly.BlockSvg.CORNER_RADIUS = 1 * Blockly.BlockSvg.GRID_UNIT; +Blockly.BlockSvg.CORNER_RADIUS = 3 * Blockly.BlockSvg.GRID_UNIT; /** * Minimum width of statement input edge on the left, in px. @@ -1707,7 +1707,7 @@ Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, cursorY) { var notchStart = ( Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.NOTCH_START_PADDING + - Blockly.BlockSvg.CORNER_RADIUS + Blockly.BlockSvg.GRID_UNIT // unknown thing ); if (this.edgeShape_ && (!this.previousConnection || !this.previousConnection.targetConnection || !this.getSurroundParent())) notchStart += this.edgeShapeWidth_; steps.push('H', notchStart, ' '); @@ -1837,7 +1837,7 @@ Blockly.BlockSvg.drawStatementInputFromTopRight_ = function(steps, cursorX, Blockly.BlockSvg.drawStatementInputTop_ = function(steps, cursorX, block, row) { steps.push(Blockly.BlockSvg.BOTTOM_RIGHT_CORNER); steps.push('H', cursorX + Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE + - 2 * Blockly.BlockSvg.CORNER_RADIUS + block.edgeShapeWidth_); + Blockly.BlockSvg.GRID_UNIT * 2 + block.edgeShapeWidth_); // if we have a custom check that corresponds to a custom notch, use it const checkStatement = (row[0].connection.check_ || []).length === 1 ? row[0].connection.check_[0] : null; @@ -1846,7 +1846,7 @@ Blockly.BlockSvg.drawStatementInputTop_ = function(steps, cursorX, block, row) { if (customNotch) notch = customNotch.right; steps.push(Blockly.pathUtils.scale(notch, 1, Blockly.BlockSvg.NOTCH_HEIGHT / (2 * Blockly.BlockSvg.GRID_UNIT))); - steps.push('h', -Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE - Blockly.BlockSvg.NOTCH_SPACING); + steps.push('h', -Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE - Blockly.BlockSvg.NOTCH_SPACING - Blockly.BlockSvg.GRID_UNIT + Blockly.BlockSvg.CORNER_RADIUS); steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER); }; @@ -1864,7 +1864,7 @@ Blockly.BlockSvg.drawStatementInputTop_ = function(steps, cursorX, block, row) { Blockly.BlockSvg.drawStatementInputBottom_ = function(steps, rightEdge, row) { steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER); if (row.statementNotchAtBottom) { - steps.push('h ', Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE + Blockly.BlockSvg.NOTCH_SPACING); + steps.push('h ', Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE + Blockly.BlockSvg.NOTCH_SPACING + Blockly.BlockSvg.GRID_UNIT - Blockly.BlockSvg.CORNER_RADIUS); // if we have a custom check that corresponds to a custom notch, use it const checkStatement = (row[0].connection.check_ || []).length === 1 ? row[0].connection.check_[0] : null; From 3f60044bcd5c7359e434d405de389c151ddfcb30 Mon Sep 17 00:00:00 2001 From: jwklong Date: Fri, 16 Oct 2026 23:08:52 +0100 Subject: [PATCH 5/5] i dont know Signed-off-by: jwklong --- core/block_render_svg.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/block_render_svg.js b/core/block_render_svg.js index 607333ed6f..b4a8191465 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -1547,7 +1547,7 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, var connectionX, connectionY; for (var y = 0, row; row = inputRows[y]; y++) { cursorX = row.paddingStart; - if (this.edgeShape_ && (!this.previousConnection || !this.previousConnection.targetConnection || !this.getSurroundParent()) && inputRows.length > 1) cursorX += this.edgeShapeWidth_ + Blockly.BlockSvg.CORNER_RADIUS * 2 + if (this.edgeShape_ && (!this.previousConnection || !this.previousConnection.targetConnection || !this.getSurroundParent()) && inputRows.length > 1) cursorX += this.edgeShapeWidth_; if (y == 0) { cursorX += this.RTL ? -iconWidth : iconWidth; }