How to create a FieldLabelSerializable Field with a custom border and background color?

334 views
Skip to first unread message

fu6...@gmail.com

unread,
Oct 11, 2025, 8:20:47 AMOct 11
to Blockly
Hello everyone,

I am currently working on replicating Scratch-style blocks using Blockly. In Scratch blocks, there are FieldLabelSerializable Fields with different backgrounds. How should I approach this programmatically?


1.png

I have already asked Gemini to generate some preliminary code, but the actual operation does not produce the correct visual output. I would be grateful for everyone's help to complete this project as soon as possible.

2.png

block.appendDummyInput()
    .appendField(new FieldLabelHexagon("AAA");


```
class FieldLabelHexagon extends Blockly.FieldLabelSerializable {
    static KEY_ = 'field_label_hexagon';
    constructor(opt_value, opt_class, opt_config) {
        super(opt_value, opt_class, opt_config);
        this.SERIALIZABLE = true;
        this.borderRectPadding_ = {
            top: 0,
            right: 12,
            bottom: 0,
            left: 12  
        };
    }

    initView() {
        super.initView();

        if (this.fieldBorderRect_) {
            this.fieldBorderRect_.remove();
        }
        this.fieldBorderRect_ = Blockly.utils.dom.createSvgElement(
            Blockly.utils.Svg.PATH,
            {
                'fill': this.sourceBlock_ ? this.sourceBlock_.getColour() : '#999999',
                'stroke': this.sourceBlock_ ? this.sourceBlock_.getColour() : '#999999',
                'class': 'blocklyFieldRect blocklyFieldLabelHexagonPath'
            },
            this.fieldGroup_
        );

        this.textElement_.style.fill = 'black';
    }

    updateSize_() {
        super.updateSize_();

        if (!this.fieldBorderRect_) {
            return;
        }

        const width = this.size_.width;
        const height = this.size_.height;
       
        const h = 10;

        const path =
            `M 0 ${height / 2} ` +
            `L ${h} 0 ` +
            `L ${width - h} 0 ` +
            `L ${width} ${height / 2} ` +
            `L ${width - h} ${height} ` +
            `L ${h} ${height} ` +
            `Z`;                  

        this.fieldBorderRect_.setAttribute('d', path);
    }
}

Blockly.FieldLabelHexagon = FieldLabelHexagon;

Blockly.registry.register(
    Blockly.registry.Type.FIELD,
    FieldLabelHexagon.KEY_,
    FieldLabelHexagon
);
``` 


Thank you all for your assistance.

fu6...  

fu6...@gmail.com

unread,
Oct 12, 2025, 8:01:28 AMOct 12
to Blockly
Hi,

I'm about to complete my project. Its main function is to provide Scratch-style blocks for students to practice with. I only have one remaining issue, which is the background style for the block palette/category area. This is the block platform I'm currently developing; please feel free to test it and provide any feedback. Thank you!

My Project

Best wishes

fu6...
fu6...@gmail.com 在 2025年10月11日 星期六晚上8:20:47 [UTC+8] 的信中寫道:
ScratchStyle.mp4
Message has been deleted

fu6...@gmail.com

unread,
Oct 13, 2025, 10:38:04 PMOct 13
to Blockly
Hi,

During the development process, I discovered an abnormal output in a Blocky or Scratch block that caused concern.

For example, the 'math_arithmetic' block when executing should output . However, Scratch outputs , while Blockly outputs '1a'. This inconsistency and non-standard behavior could lead to students developing incorrect learning/understanding.

If it produced , the Scratch program should ideally be stopped, rather than outputting to allow Scratch to continue executing erroneously. This potentially leads to students forming poor programming habits or being unable to find errors, which is a detrimental outcome.


Blockly_Error.png
Scratch_Error.jpg

fu6...
fu6...@gmail.com 在 2025年10月12日 星期日晚上8:01:28 [UTC+8] 的信中寫道:
Message has been deleted

fu6...@gmail.com

unread,
Oct 15, 2025, 8:44:16 AMOct 15
to Blockly
Hi,

  I successfully solved the programming problem.  


screenshot.png


Blockly.Blocks['test'] = {
  init: function() {
    this.appendDummyInput()
.appendField(new FieldZelosLabelBackground('Taiwan', null, {
textColor: '#FFFFFF',
backgroundColor: '#FD6723',
shapeType: 0
}));
    this.appendDummyInput()
.appendField(new FieldZelosLabelBackground('Hello', null, {
textColor: '#FFFFFF',
backgroundColor: '#CC9900',
shapeType: 1
}));
    this.appendDummyInput()
.appendField(new FieldZelosLabelBackground('World', null, {
textColor: '#FFFFFF',
backgroundColor: '#59C059',
shapeType: 2
}));
    this.setInputsInline(true);  
    this.setPreviousStatement(0);
    this.setNextStatement(!0);
    this.setStyle('control_blocks');
  }
};





class FieldZelosLabelBackground extends Blockly.FieldLabelSerializable {
    static KEY_ = 'field_zelos_label_background';

    static SHAPE_TYPES = {
        SQUARE: 0,
        OVAL: 1,
        HEXAGON: 2
    };

static getRoundRectPath(x, y, width, height, radius) {
return `M ${x + radius},${y}
h ${width - 2 * radius}
a ${radius},${radius} 0 0 1 ${radius},${radius}
v ${height - 2 * radius}
a ${radius},${radius} 0 0 1 -${radius},${radius}
h -${width - 2 * radius}
a ${radius},${radius} 0 0 1 -${radius},-${radius}
v -${height - 2 * radius}
a ${radius},${radius} 0 0 1 ${radius},-${radius}
z`;
}

static getHexagonPath(width, height) {
const slant = height / 2;

const x = 0;
const y = 0;

const path = [
`M ${x + slant},${y}`,
`l ${width},0`,
`l ${slant},${height / 2}`,
`l -${slant},${height / 2}`,
`l -${width},0`,
`l -${slant},-${height / 2}`,
];

return path.join(' ');
}


    constructor(value, opt_class, opt_config) {
        super(value || '  ', null, opt_config);

opt_config = opt_config || {};

this.textColor_ = opt_config.textColor || '#FFFFFF';
this.backgroundColor_ = opt_config.backgroundColor || '#FD6723';
this.backgroundStyle_ = opt_config.shapeType || 0;

    }

    initView() {
        super.initView();
       
        if (this.fieldBorderRect_) {
            this.fieldBorderRect_.remove();
        }

        this.fieldBorderRect_ = Blockly.utils.dom.createSvgElement(
            Blockly.utils.Svg.PATH,
            {
                'class': 'blocklyFieldRect blocklyFieldZelosOvalPath',
                'fill': '#FFFFFF',
                'stroke': '#FFFFFF'
            },
            this.fieldGroup_
        );
       
        this.fieldGroup_.insertBefore(this.fieldBorderRect_, this.textElement_);
        this.textElement_.style.fill = this.textColor_;
        this.applyColour();
    }

    applyColour() {
        if (!this.fieldBorderRect_) {
            return;
        }

        this.fieldBorderRect_.setAttribute('fill', this.backgroundColor_);
        this.fieldBorderRect_.setAttribute('stroke', this.backgroundColor_);

        if (this.textElement_) {
             this.textElement_.style.fill = this.textColor_;
        }
    }
   
    showEditor_() {
     
    }

    updateSize_() {
        super.updateSize_();

        if (!this.fieldBorderRect_ || !this.sourceBlock_ || !this.sourceBlock_.workspace.getRenderer()) {
            return;
        }

        const renderer = this.sourceBlock_.workspace.getRenderer();
        const constants = renderer.getConstants ? renderer.getConstants() : renderer.constants_;

const textWidth = this.textElement_.getComputedTextLength();
let fieldHeight = 30;
let cornerRadius = 0;
let finalWidth = textWidth;
let gRectPath = "";
let paddingLeft = 0;

if (this.backgroundStyle_==0) { // Square
cornerRadius = fieldHeight / 6;
finalWidth = textWidth + cornerRadius*2;
gRectPath = FieldZelosLabelBackground.getRoundRectPath(
0, 0,
finalWidth,
fieldHeight,
cornerRadius
);
paddingLeft = 5;
} else if (this.backgroundStyle_==1) { // Oval
cornerRadius = fieldHeight / 2;
finalWidth = textWidth + cornerRadius*2;
gRectPath = FieldZelosLabelBackground.getRoundRectPath(
0, 0,
finalWidth,
fieldHeight,
cornerRadius
);
paddingLeft = 14;
} else if (this.backgroundStyle_==2) { // Hexagon
finalWidth = textWidth + fieldHeight;
gRectPath = FieldZelosLabelBackground.getHexagonPath(textWidth, fieldHeight);
paddingLeft = 14;
}
this.fieldBorderRect_.setAttribute('d', gRectPath);

this.size_.width = finalWidth;
       
        const translateX = 0;
        const translateY = -4;
       
        this.fieldBorderRect_.setAttribute('transform', `translate(${translateX}, ${translateY})`);

        this.textElement_.setAttribute('x', paddingLeft);
       
        this.applyColour();

    }
}

Blockly.FieldZelosLabelBackground = FieldZelosLabelBackground;

Blockly.registry.register(
    Blockly.registry.Type.FIELD,
    FieldZelosLabelBackground.KEY_,
    FieldZelosLabelBackground
);


fu6...
fu6...@gmail.com 在 2025年10月15日 星期三下午4:05:01 [UTC+8] 的信中寫道:
H i,

I have successfully created a new field inheriting from FieldLabelSerializable to display an oval background with text, and this field allows for custom background and text colors. If there are any best practices or correct implementation details for this code, please feel free to guide me. The next step is to extend this program to create a field where the background shape can be configured as an oval, a diamond, or a rectangle.

FieldZelosOvalBackground.png


blocks.js

Blockly.Blocks['test'] = {
  init: function() {
    this.appendDummyInput()
        .appendField(new FieldZelosOvalBackground('Taiwan', '#FFFFFF', '#FD6723', null));
    this.appendDummyInput()
        .appendField(new FieldZelosOvalBackground('Hello', '#FFFFFF', '#CC9900', null));
    this.appendDummyInput()
        .appendField(new FieldZelosOvalBackground('World', '#FFFFFF', '#59C059', null));
    this.setPreviousStatement(0);
    this.setNextStatement(!0);
    this.setStyle('control_blocks');
  }
};



Blocklycompressed.js

class FieldZelosOvalBackground extends Blockly.FieldLabelSerializable {
    static KEY_ = 'field_zelos_oval_bg';

static getRoundRectPath(x, y, width, height, radius) {
return `M ${x + radius},${y}
h ${width - 2 * radius}
a ${radius},${radius} 0 0 1 ${radius},${radius}
v ${height - 2 * radius}
a ${radius},${radius} 0 0 1 -${radius},${radius}
h -${width - 2 * radius}
a ${radius},${radius} 0 0 1 -${radius},-${radius}
v -${height - 2 * radius}
a ${radius},${radius} 0 0 1 ${radius},-${radius}
z`;
}

    constructor(value, text_color, background_color, opt_config) {
        super(value || '  ', opt_config);
        this.textColor_ = text_color || '#FFFFFF';
        this.backgroundColor_ = background_color || '#FD6723';

    }

    initView() {
        super.initView();
       
        if (this.fieldBorderRect_) {
            this.fieldBorderRect_.remove();
        }

        this.fieldBorderRect_ = Blockly.utils.dom.createSvgElement(
            Blockly.utils.Svg.PATH,
            {
                'class': 'blocklyFieldRect blocklyFieldZelosOvalPath',
                'fill': '#FFFFFF',
                'stroke': '#FFFFFF'
            },
            this.fieldGroup_
        );
       
        this.fieldGroup_.insertBefore(this.fieldBorderRect_, this.textElement_);
        this.textElement_.style.fill = this.textColor_;
        this.applyColour();
    }

    applyColour() {
        if (!this.fieldBorderRect_) {
            return;
        }

        this.fieldBorderRect_.setAttribute('fill', this.backgroundColor_);
        this.fieldBorderRect_.setAttribute('stroke', this.backgroundColor_);

        if (this.textElement_) {
             this.textElement_.style.fill = this.textColor_;
        }
    }
   
    showEditor_() {
     
    }

    updateSize_() {
        super.updateSize_();

        if (!this.fieldBorderRect_ || !this.sourceBlock_ || !this.sourceBlock_.workspace.getRenderer()) {
            return;
        }

        const renderer = this.sourceBlock_.workspace.getRenderer();
        const constants = renderer.getConstants ? renderer.getConstants() : renderer.constants_;

        let fieldHeight = 30;
const cornerRadius = fieldHeight / 2;

const textWidth = this.textElement_.getComputedTextLength();
       
        const totalWidth = textWidth + cornerRadius*2;

        this.size_.width = totalWidth;
       
        const d = FieldZelosOvalBackground.getRoundRectPath(
            0, 0,
            totalWidth,
            fieldHeight,
            cornerRadius
        );

        this.fieldBorderRect_.setAttribute('d', d);
       
        const translateX = 0;
        const translateY = -4;
       
        this.fieldBorderRect_.setAttribute('transform', `translate(${translateX}, ${translateY})`);

let textPadding = 14;
        this.textElement_.setAttribute('x', textPadding);
       
        this.applyColour();
    }
}

Blockly.FieldZelosOvalBackground = FieldZelosOvalBackground;

Blockly.registry.register(
    Blockly.registry.Type.FIELD,
    FieldZelosOvalBackground.KEY_,
    FieldZelosOvalBackground
);


fu6...



fu6...@gmail.com 在 2025年10月14日 星期二上午10:38:04 [UTC+8] 的信中寫道:

Max Stephen Russell

unread,
Oct 15, 2025, 9:10:33 AMOct 15
to blo...@googlegroups.com
If I ever deal with this particular issue, I will come back to your post. It looks perfect in every way. It’s elegant, it’s organized, it’s intelligent, it’s . . . what I would hope to find, even if it had an error in it. 😎

-Steve Russell


--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/blockly/ba7938c6-6cff-4c80-887a-311d091995fcn%40googlegroups.com.

fu6...@gmail.com

unread,
Oct 16, 2025, 7:17:38 AMOct 16
to Blockly
Hey everyone!

I'm sharing my new custom field for Blockly v10.0.0 and the Zelos Renderer: a diamond-shaped background! Feel free to use. If you are using a Blockly version other than v10, please be aware that some code adjustments or value corrections may be necessary to ensure the field runs and displays correctly in your environment. Future updates will be posted here.

fuFieldZelosLabelBackground.png.png

fu6...

maxsr...@gmail.com 在 2025年10月15日 星期三晚上9:10:33 [UTC+8] 的信中寫道:

fu6...@gmail.com

unread,
Oct 16, 2025, 10:26:41 PMOct 16
to Blockly
Hi,

Hello everyone,

I'm pleased to announce the successful completion of the custom variable type implementation, which mimics the design of Scratch's "My Blocks" (Procedures).

This project required modifying the core Blockly logic because, in version v10.0.0, the default value for newly created variable types is an empty string (''). Furthermore, the crucial type attribute was not being fully implemented across the mutationToDom/domToMutation methods and the XML mutation argument structure, which caused issues with loading order.

With the crucial assistance of Gemini, I have finalized the necessary core program enhancements to resolve these serialization and synchronization conflicts.

Please feel free to check out the results!

The operation for adding new functions is shown in the video, and I will also attempt to enable the default Mutator feature of procedures_defnoreturn in the future for content modification.


fu6...

fu6...@gmail.com 在 2025年10月16日 星期四晚上7:17:38 [UTC+8] 的信中寫道:
MyBlocks.mp4

Seb Seb

unread,
Oct 17, 2025, 5:52:38 AMOct 17
to Blockly
That's great! Wonderful job, as usual.
Thanks a lot.

fu6...@gmail.com

unread,
Oct 17, 2025, 11:37:58 AMOct 17
to Blockly
Hi all,

I'm excited to share a new custom field I've implemented: FieldZelosInputBackground. It extends Blockly.FieldTextInput.

This field allows you to set a unique background shape and color for the input area, which is visible when the field is static (i.e., before it gains focus/is clicked).

You can find the sample code for FieldZelosInputBackground here.

fu6...

prt...@gmail.com 在 2025年10月17日 星期五下午5:52:38 [UTC+8] 的信中寫道:
fuFieldZelosInputBackground.mp4

fu6...@gmail.com

unread,
Oct 20, 2025, 4:22:14 AMOct 20
to Blockly
I have successfully completed the modification of the procedure blocks to be consistent with Scratch's design. This is a cause for celebration!

scratchCap.png  

fu6...@gmail.com 在 2025年10月17日 星期五晚上11:37:58 [UTC+8] 的信中寫道:

Max Stephen Russell

unread,
Oct 20, 2025, 5:44:27 AMOct 20
to blo...@googlegroups.com
It LOOKS like a celebration. You do great work.


Message has been deleted

fu6...@gmail.com

unread,
Oct 21, 2025, 12:36:32 PMOct 21
to Blockly
Hi, 

My implementation of a custom block field now enables a Scratch-like workflow: users can click and drag directly from the field on a function block to create and start dragging a variable getter block. The new block is instantiated and immediately enters the drag state for seamless placement.

This is a link to my code sample. (Blockly v10)

fu6...@gmail.com 在 2025年10月21日 星期二晚上9:07:04 [UTC+8] 的信中寫道:
I have successfully mimicked a Scratch functionality where a double-click on a function block's variable field (a FieldLabel) dynamically spawns a variable getter block at the precise cursor location. I have not yet managed to implement the variable block creation using Scratch's signature drag-and-drop mechanism. Please refer to the video demonstration.

maxsr...@gmail.com 在 2025年10月20日 星期一下午5:44:27 [UTC+8] 的信中寫道:
Scratch-like.mp4

fu6...@gmail.com

unread,
Oct 24, 2025, 7:12:36 AM (12 days ago) Oct 24
to Blockly
Hello everyone,

I currently have one final issue left to solve in our Scratch-Like block development project, and despite many attempts, I haven't been able to pinpoint the key to the solution. I am reaching out to the community for guidance.

The problem, as demonstrated in the attached video, is as follows:

When I move a block near an input connection point where it should snap, I execute the highlight() command. This command is intended to display the yellow highlighted outline (or connection preview) around the target connection area. However, it only renders a small yellow dot and simultaneously throws an error message.

I would greatly appreciate any insights or guidance to resolve this as soon as possible. Thank you in advance for your help.    The link below is my code.  

1.png

2.png
Error Message

m 0,-5  v 5 pathDown(d){return a(d,!1,!1)} v 5 
3.png


fu6...

fu6...@gmail.com 在 2025年10月22日 星期三凌晨12:36:32 [UTC+8] 的信中寫道:
highlight.mp4

fu6...@gmail.com

unread,
Oct 25, 2025, 7:12:11 AM (11 days ago) Oct 25
to Blockly
Hello everyone,

I have successfully modified the code to resolve the issue where the highlight feature failed to appear when approaching the oval-shaped input field.
However, I am now facing a new problem: when moving a Boolean block, the targetConnection property unexpectedly returns null, thus preventing the highlight() function from executing correctly.


blockBoolean_error.png

fu6....

fu6...@gmail.com 在 2025年10月24日 星期五晚上7:12:36 [UTC+8] 的信中寫道:
Boolean_error.mp4

fu6...@gmail.com

unread,
Oct 26, 2025, 3:44:34 AM (10 days ago) Oct 26
to Blockly
Hi,

I have implemented a custom highlightPath logic to temporarily resolve the issue of the connection highlight failing to render when an input field lacks a Shadow block or an targetConnection. Here is the code I am using.

However, a significant and persistent issue remains: the workspace frequently experiences an unexpected rightward drift during user interaction. This behavior is clearly demonstrated in the accompanying video file.


fu6...

fu6...@gmail.com 在 2025年10月25日 星期六晚上7:12:11 [UTC+8] 的信中寫道:
noShadowHighlight.mp4
Reply all
Reply to author
Forward
0 new messages