Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow configuration of buttons, pixel colors, and fixed field height for Bitmap field #2351

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion plugins/field-bitmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,24 @@ npm install @blockly/field-bitmap --save

## Usage

This field accepts up to 3 parameters:
This field accepts up to 6 parameters:

- `"value"` to specify an initial value. Must be a 2D rectangular array of 1s and 0s.
If not provided, the default is an empty grid of the specified size.
- `"width"` to specify an initial width, if there is no initial value.
If not provided, the default is a width of 5.
- `"height"` to specify an initial height, if there is no initial value.
If not provided, the default is a height of 5.
- `fieldHeight"` to specify a static field height. If provided, the individual pixels
will be resized to fit inside the field. This only affects the field as it is
seen on a block and not the pop-up editor. Good for larger images. (_Note: If this
results in fractional pixel sizes, the overall field height may not exactly match
the specified value on all browsers._)
- `"colours"` to override the default colours, Default values:
`{filled: '#363d80', empty: '#fff'}`
- `"buttons"` to show or hide the "Randomize" and/or "Clear" buttons. If either is
omitted, the button will be shown. Default values:
`{randomize: true, clear: true}`

### JavaScript

Expand Down
92 changes: 68 additions & 24 deletions plugins/field-bitmap/src/field-bitmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@

import * as Blockly from 'blockly/core';

Blockly.Msg['BUTTON_LABEL_RANDOMIZE'] = 'Randomize';
Blockly.Msg['BUTTON_LABEL_CLEAR'] = 'Clear';
Comment on lines +9 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't noticed this previously. I think it's fine as it is for the moment, but the team should probably make some decisions about how we will deal with localisation of plugins now that more formerly-core functionality has been moved out of Blockly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this, I was following guidance from @maribethb who pointed out that this is how it’s done elsewhere:

https://google.github.io/blockly-samples/plugins/block-plus-minus/README

Blockly.Msg['PROCEDURE_VARIABLE'] = 'variable:';

Naturally, I’m open to changing this if there’s a better way. I had originally set up to take in the translation strings as configuration options (which is why there was ‘buttonOptions’ initially).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Her guidance is certainly good. I've no issue with the code; my comment was more as a reminder to myself that we should probably think about whether we want to add messages like this to the set that get translated by translatewiki. Until now we've only had messages in core Blockly and Blockly Games translated, not messages for plugins—but with some formerly-core fields and blocks now being moved to plugins we should probably revisit that.


export const DEFAULT_HEIGHT = 5;
export const DEFAULT_WIDTH = 5;
const PIXEL_SIZE = 15;
const FILLED_PIXEL_COLOR = '#363d80';
const EMPTY_PIXEL_COLOR = '#fff';

const DEFAULT_PIXEL_SIZE = 15;
const DEFAULT_PIXEL_COLOURS: PixelColours = {
empty: '#fff',
filled: '#363d80',
};
const DEFAULT_BUTTONS: Buttons = {
randomize: true,
clear: true,
};
/**
* Field for inputting a small bitmap image.
* Includes a grid of clickable pixels that's exported as a bitmap.
Expand All @@ -31,6 +39,9 @@ export class FieldBitmap extends Blockly.Field<number[][]> {
/** Stateful variables */
private mouseIsDown = false;
private valToPaintWith?: number;
buttonOptions: Buttons;
pixelSize: number;
pixelColours: {empty: string; filled: string};

/**
* Constructor for the bitmap field.
Expand All @@ -48,6 +59,8 @@ export class FieldBitmap extends Blockly.Field<number[][]> {

this.SERIALIZABLE = true;
this.CURSOR = 'default';
this.buttonOptions = {...DEFAULT_BUTTONS, ...config?.buttons};
this.pixelColours = {...DEFAULT_PIXEL_COLOURS, ...config?.colours};

// Configure value, height, and width
const currentValue = this.getValue();
Expand All @@ -60,6 +73,11 @@ export class FieldBitmap extends Blockly.Field<number[][]> {
// Set a default empty value
this.setValue(this.getEmptyArray());
}
if (config?.fieldHeight) {
this.pixelSize = config.fieldHeight / this.imgHeight;
cpcallen marked this conversation as resolved.
Show resolved Hide resolved
} else {
this.pixelSize = DEFAULT_PIXEL_SIZE;
}
}

/**
Expand Down Expand Up @@ -186,13 +204,13 @@ export class FieldBitmap extends Blockly.Field<number[][]> {

if (this.blockDisplayPixels) {
this.blockDisplayPixels[r][c].style.fill = pixel
? FILLED_PIXEL_COLOR
: EMPTY_PIXEL_COLOR;
? this.pixelColours.filled
: this.pixelColours.empty;
}
if (this.editorPixels) {
this.editorPixels[r][c].style.background = pixel
? FILLED_PIXEL_COLOR
: EMPTY_PIXEL_COLOR;
? this.pixelColours.filled
: this.pixelColours.empty;
}
});
}
Expand Down Expand Up @@ -266,9 +284,11 @@ export class FieldBitmap extends Blockly.Field<number[][]> {
this.editorPixels[r].push(button);
rowDiv.appendChild(button);

// Load the current pixel color
// Load the current pixel colour
const isOn = this.getPixel(r, c);
button.style.background = isOn ? FILLED_PIXEL_COLOR : EMPTY_PIXEL_COLOR;
button.style.background = isOn
? this.pixelColours.filled
: this.pixelColours.empty;

// Handle clicking a pixel
this.bindEvent(button, 'mousedown', () => {
Expand All @@ -285,16 +305,28 @@ export class FieldBitmap extends Blockly.Field<number[][]> {
}

// Add control buttons below the pixel grid
this.addControlButton(dropdownEditor, 'Randomize', this.randomizePixels);
this.addControlButton(dropdownEditor, 'Clear', this.clearPixels);
if (this.buttonOptions.randomize) {
this.addControlButton(
dropdownEditor,
Blockly.Msg['BUTTON_LABEL_RANDOMIZE'],
this.randomizePixels,
);
}
if (this.buttonOptions.clear) {
this.addControlButton(
dropdownEditor,
Blockly.Msg['BUTTON_LABEL_CLEAR'],
this.clearPixels,
);
}

if (this.blockDisplayPixels) {
this.forAllCells((r, c) => {
const pixel = this.getPixel(r, c);
if (this.editorPixels) {
this.editorPixels[r][c].style.background = pixel
? FILLED_PIXEL_COLOR
: EMPTY_PIXEL_COLOR;
? this.pixelColours.filled
: this.pixelColours.empty;
}
});
}
Expand All @@ -316,11 +348,11 @@ export class FieldBitmap extends Blockly.Field<number[][]> {
const square = Blockly.utils.dom.createSvgElement(
'rect',
{
x: c * PIXEL_SIZE,
y: r * PIXEL_SIZE,
width: PIXEL_SIZE,
height: PIXEL_SIZE,
fill: EMPTY_PIXEL_COLOR,
x: c * this.pixelSize,
y: r * this.pixelSize,
width: this.pixelSize,
height: this.pixelSize,
fill: this.pixelColours.empty,
fill_opacity: 1, // eslint-disable-line
},
this.getSvgRoot(),
Expand All @@ -337,8 +369,8 @@ export class FieldBitmap extends Blockly.Field<number[][]> {
// eslint-disable-next-line
protected override updateSize_() {
{
const newWidth = PIXEL_SIZE * this.imgWidth;
const newHeight = PIXEL_SIZE * this.imgHeight;
const newWidth = this.pixelSize * this.imgWidth;
const newHeight = this.pixelSize * this.imgHeight;
if (this.borderRect_) {
this.borderRect_.setAttribute('width', String(newWidth));
this.borderRect_.setAttribute('height', String(newHeight));
Expand Down Expand Up @@ -552,10 +584,22 @@ export class FieldBitmap extends Blockly.Field<number[][]> {
}
}

interface Buttons {
readonly randomize: boolean;
readonly clear: boolean;
}
interface PixelColours {
readonly empty: string;
readonly filled: string;
}

export interface FieldBitmapFromJsonConfig extends Blockly.FieldConfig {
value?: number[][];
width?: number;
height?: number;
buttons?: Buttons;
fieldHeight?: number;
colours?: PixelColours;
}

Blockly.fieldRegistry.register('field_bitmap', FieldBitmap);
Expand All @@ -579,11 +623,11 @@ Blockly.Css.register(`
flex-direction: row;
padding: 0;
margin: 0;
height: ${PIXEL_SIZE}
height: ${DEFAULT_PIXEL_SIZE}
}
.pixelButton {
width: ${PIXEL_SIZE}px;
height: ${PIXEL_SIZE}px;
width: ${DEFAULT_PIXEL_SIZE}px;
height: ${DEFAULT_PIXEL_SIZE}px;
border: 1px solid #000;
}
.pixelDisplay {
Expand Down
36 changes: 36 additions & 0 deletions plugins/field-bitmap/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,42 @@ const toolbox = generateFieldTestBlocks('field_bitmap', [
value: undefined,
},
},
{
label: 'Static field height, custom colors, no buttons',
args: {
value: [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
],
fieldHeight: 50,
buttons: {
randomize: false,
clear: false,
},
colours: {
filled: '#4888f4',
empty: '#cad2dc',
},
},
},
]);

/**
Expand Down
Loading