Skip to content

Commit 043c06e

Browse files
authored
extend matrix field to support editing sprites (#11296)
* extend led matrix field to support colors * add more options
1 parent 11c03ac commit 043c06e

10 files changed

Lines changed: 335 additions & 57 deletions

File tree

localtypings/pxtarget.d.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -906,13 +906,7 @@ declare namespace ts.pxtc {
906906
icon?: string;
907907
jresURL?: string;
908908
iconURL?: string;
909-
imageLiteral?: number;
910-
gridLiteral?: number;
911-
gridLiteralOnColor?: string;
912-
gridLiteralOffColor?: string;
913-
imageLiteralColumns?: number; // optional number of columns
914-
imageLiteralRows?: number; // optional number of rows
915-
imageLiteralScale?: number; // button sizing between 0.6 and 2, default is 1
909+
916910
weight?: number;
917911
parts?: string;
918912
hiddenParts?: string; // allows an extesion to declaratively hide a part
@@ -989,6 +983,23 @@ declare namespace ts.pxtc {
989983

990984
/* end enum-only attributes */
991985

986+
/* led matrix field editor attributes */
987+
988+
imageLiteral?: number;
989+
gridLiteral?: number;
990+
colorGridLiteral?: number;
991+
gridLiteralPalette?: string;
992+
gridLiteralPaletteNames?: string;
993+
gridLiteralUseProjectPalette?: boolean;
994+
gridLiteralOnColor?: string;
995+
gridLiteralOffColor?: string;
996+
gridLiteralVerticalSpacing?: number; // optional spacing between pixels, default is 5
997+
gridLiteralHorizontalSpacing?: number; // optional spacing between pixels, default is 7
998+
gridLiteralBorderRadius?: number; // optional border radius for pixels, default is 5
999+
imageLiteralColumns?: number; // optional number of columns
1000+
imageLiteralRows?: number; // optional number of rows
1001+
imageLiteralScale?: number; // button sizing between 0.6 and 2, default is 1
1002+
9921003

9931004
isKind?: boolean; // annotation for built-in kinds in library code
9941005
kindMemberName?: string; // The name a member of the kind as it will appear in the blocks editor. If the kind was "Colors" this would be "color"

pxtblocks/compiler/compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ function compileImage(e: Environment, b: Blockly.Block, frames: number, columns:
809809
for (let j = 0; j < columns; ++j) {
810810
if (j > 0)
811811
state += ' ';
812-
state += (leds[(i * columns) + j] === '#') ? "#" : ".";
812+
state += leds[(i * columns) + j];
813813
}
814814
state += '\n';
815815
}

pxtblocks/compiler/environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ export function mkEnv(w: Blockly.Workspace, blockInfo?: pxtc.BlocksInfo, options
203203
attrs: fn.attributes,
204204
isExtensionMethod: instance,
205205
isExpression: fn.retType && fn.retType !== "void",
206-
imageLiteral: fn.attributes.imageLiteral || fn.attributes.gridLiteral,
206+
imageLiteral: fn.attributes.imageLiteral || fn.attributes.gridLiteral || fn.attributes.colorGridLiteral,
207207
imageLiteralColumns: fn.attributes.imageLiteralColumns,
208208
imageLiteralRows: fn.attributes.imageLiteralRows,
209209
hasHandler: pxt.blocks.hasHandler(fn),

pxtblocks/fields/field_ledmatrix.ts

Lines changed: 92 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/// <reference path="../../built/pxtsim.d.ts" />
33

44
import * as Blockly from "blockly";
5+
import { DEFAULT_LED_COLORS } from "./field_ledmatrix_colorPicker";
56
import { FieldMatrix } from "./field_matrix";
67
import { FieldCustom } from "./field_utils";
78

@@ -27,9 +28,12 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
2728
public SERIALIZABLE = true;
2829

2930
private params: any;
30-
private onColor = "#FFFFFF";
31-
private offColor: string;
31+
32+
private palette: string[];
33+
3234
private static DEFAULT_OFF_COLOR = "#000000";
35+
private static DEFAULT_ON_COLOR = "#FFFFFF";
36+
private offOpacity = 0.2;
3337

3438
private scale = 1;
3539

@@ -39,13 +43,18 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
3943
private yAxisLabel: LabelMode = LabelMode.None;
4044
private xAxisLabel: LabelMode = LabelMode.None;
4145

42-
private cellState: boolean[][] = [];
46+
private cellState: number[][] = [];
4347

4448
private currentDragState_: boolean;
4549

4650
protected clearSelectionOnBlur = true;
4751
protected forceFocusVisible = true;
4852

53+
protected isColorMatrix = false;
54+
protected colorNames: string[];
55+
56+
private activeColor = 1;
57+
4958
constructor(text: string, params: any, validator?: Blockly.FieldValidator) {
5059
super(text, validator);
5160
this.params = params;
@@ -64,12 +73,37 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
6473
}
6574
}
6675

67-
if (this.params.onColor !== undefined) {
68-
this.onColor = this.params.onColor;
76+
this.isColorMatrix = !!this.params.isColorMatrix;
77+
78+
if (this.params.colors) {
79+
this.palette = this.params.colors;
80+
}
81+
else {
82+
this.palette = [
83+
FieldLedMatrix.DEFAULT_OFF_COLOR,
84+
...DEFAULT_LED_COLORS
85+
];
86+
}
87+
88+
if (this.params.colorNames) {
89+
this.colorNames = this.params.colorNames;
90+
}
91+
else {
92+
this.colorNames = [
93+
lf("off"),
94+
...DEFAULT_LED_COLORS
95+
];
96+
}
97+
98+
if (this.params.hasOffColor) {
99+
this.offOpacity = 1.0;
69100
}
70101

71-
if (this.params.offColor !== undefined) {
72-
this.offColor = this.params.offColor;
102+
if (this.params.offOpacity) {
103+
const val = parseFloat(this.params.offOpacity);
104+
if (!isNaN(val) && val >= 0 && val <= 1) {
105+
this.offOpacity = val;
106+
}
73107
}
74108

75109
if (this.params.scale !== undefined)
@@ -79,18 +113,27 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
79113
else if (Math.max(this.numMatrixCols, this.numMatrixRows) > 10)
80114
this.scale = 0.9;
81115

82-
this.size_.height = this.scale * Number(this.numMatrixRows) * (FieldLedMatrix.CELL_WIDTH + FieldLedMatrix.CELL_VERTICAL_MARGIN) + FieldLedMatrix.CELL_VERTICAL_MARGIN * 2 + FieldLedMatrix.BOTTOM_MARGIN + this.getXAxisHeight()
83-
this.size_.width = this.scale * Number(this.numMatrixCols) * (FieldLedMatrix.CELL_WIDTH + FieldLedMatrix.CELL_HORIZONTAL_MARGIN) + FieldLedMatrix.CELL_HORIZONTAL_MARGIN + this.getYAxisWidth();
116+
const verticalMargin = isNaN(this.params.verticalSpacing) ? FieldLedMatrix.CELL_VERTICAL_MARGIN : this.params.verticalSpacing;
117+
const horizontalMargin = isNaN(this.params.horizontalSpacing) ? FieldLedMatrix.CELL_HORIZONTAL_MARGIN : this.params.horizontalSpacing;
118+
119+
this.size_.height = this.scale * Number(this.numMatrixRows) * (FieldLedMatrix.CELL_WIDTH + verticalMargin) + verticalMargin * 2 + FieldLedMatrix.BOTTOM_MARGIN + this.getXAxisHeight()
120+
this.size_.width = this.scale * Number(this.numMatrixCols) * (FieldLedMatrix.CELL_WIDTH + horizontalMargin) + horizontalMargin + this.getYAxisWidth();
84121
}
85122

86123
protected getCellToggled(x: number, y: number): boolean {
87-
return this.cellState[x][y];
124+
return !!this.cellState[x][y];
88125
}
89126

90127
protected useTwoToneFocusIndicator(x: number, y: number): boolean {
91128
return this.getCellToggled(x, y);
92129
}
93130

131+
setActiveColorIndex(index: number) {
132+
if (index >= 0 && index < this.palette.length) {
133+
this.activeColor = index;
134+
}
135+
}
136+
94137
/**
95138
* Show the inline free-text editor on top of the text.
96139
* @private
@@ -136,31 +179,34 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
136179
for (let i = 0; i < this.numMatrixCols; i++) {
137180
this.cellState.push([])
138181
for (let j = 0; j < this.numMatrixRows; j++) {
139-
this.cellState[i].push(false);
182+
this.cellState[i].push(0);
140183
}
141184
}
142185

143186
this.restoreStateFromString();
144187

188+
const verticalMargin = isNaN(this.params.verticalSpacing) ? FieldLedMatrix.CELL_VERTICAL_MARGIN : this.params.verticalSpacing;
189+
const horizontalMargin = isNaN(this.params.horizontalSpacing) ? FieldLedMatrix.CELL_HORIZONTAL_MARGIN : this.params.horizontalSpacing;
190+
145191
this.createMatrixDisplay({
146192
cellWidth: FieldLedMatrix.CELL_WIDTH,
147193
cellHeight: FieldLedMatrix.CELL_WIDTH,
148194
cellLabel: lf("LED"),
149-
cellHorizontalMargin: FieldLedMatrix.CELL_HORIZONTAL_MARGIN,
150-
cellVerticalMargin: FieldLedMatrix.CELL_VERTICAL_MARGIN,
151-
cornerRadius: FieldLedMatrix.CELL_CORNER_RADIUS,
152-
cellFill: this.offColor,
195+
cellHorizontalMargin: horizontalMargin,
196+
cellVerticalMargin: verticalMargin,
197+
cornerRadius: isNaN(this.params.borderRadius) ? FieldLedMatrix.CELL_CORNER_RADIUS : this.params.borderRadius,
198+
cellFill: this.palette[0],
153199
padLeft: this.getYAxisWidth(),
154200
scale: this.scale
155201
});
156202

157203
this.updateValue();
158204

159205
if (this.xAxisLabel !== LabelMode.None) {
160-
const y = this.scale * this.numMatrixRows * (FieldLedMatrix.CELL_WIDTH + FieldLedMatrix.CELL_VERTICAL_MARGIN) + FieldLedMatrix.CELL_VERTICAL_MARGIN * 2 + FieldLedMatrix.BOTTOM_MARGIN
206+
const y = this.scale * this.numMatrixRows * (FieldLedMatrix.CELL_WIDTH + verticalMargin) + verticalMargin * 2 + FieldLedMatrix.BOTTOM_MARGIN
161207
const xAxis = pxsim.svg.child(this.matrixSvg, "g", { transform: `translate(${0} ${y})` });
162208
for (let i = 0; i < this.numMatrixCols; i++) {
163-
const x = this.getYAxisWidth() + this.scale * i * (FieldLedMatrix.CELL_WIDTH + FieldLedMatrix.CELL_HORIZONTAL_MARGIN) + FieldLedMatrix.CELL_WIDTH / 2 + FieldLedMatrix.CELL_HORIZONTAL_MARGIN / 2;
209+
const x = this.getYAxisWidth() + this.scale * i * (FieldLedMatrix.CELL_WIDTH + horizontalMargin) + FieldLedMatrix.CELL_WIDTH / 2 + horizontalMargin / 2;
164210
const lbl = pxsim.svg.child(xAxis, "text", { x, class: "blocklyText" })
165211
lbl.textContent = this.getLabel(i, this.xAxisLabel);
166212
}
@@ -169,7 +215,7 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
169215
if (this.yAxisLabel !== LabelMode.None) {
170216
const yAxis = pxsim.svg.child(this.matrixSvg, "g", {});
171217
for (let i = 0; i < this.numMatrixRows; i++) {
172-
const y = this.scale * i * (FieldLedMatrix.CELL_WIDTH + FieldLedMatrix.CELL_VERTICAL_MARGIN) + FieldLedMatrix.CELL_WIDTH / 2 + FieldLedMatrix.CELL_VERTICAL_MARGIN * 2;
218+
const y = this.scale * i * (FieldLedMatrix.CELL_WIDTH + verticalMargin) + FieldLedMatrix.CELL_WIDTH / 2 + verticalMargin * 2;
173219
const lbl = pxsim.svg.child(yAxis, "text", { x: 0, y, class: "blocklyText" })
174220
lbl.textContent = this.getLabel(i, this.yAxisLabel);
175221
}
@@ -265,7 +311,7 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
265311
}
266312

267313
protected toggleCell = (x: number, y: number, value?: boolean) => {
268-
this.cellState[x][y] = value ?? this.currentDragState_;
314+
this.cellState[x][y] = (value ?? this.currentDragState_) ? this.activeColor : 0;
269315
this.updateValue();
270316
}
271317

@@ -293,11 +339,11 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
293339
}
294340

295341
private getColor(x: number, y: number) {
296-
return this.cellState[x][y] ? this.onColor : (this.offColor || FieldLedMatrix.DEFAULT_OFF_COLOR);
342+
return this.palette[this.cellState[x][y]];
297343
}
298344

299345
private getOpacity(x: number, y: number) {
300-
const offOpacity = this.offColor ? '1.0': '0.2';
346+
const offOpacity = this.offOpacity + "";
301347
return this.cellState[x][y] ? '1.0' : offOpacity;
302348
}
303349

@@ -306,7 +352,10 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
306352
cellRect.setAttribute("fill", this.getColor(x, y));
307353
cellRect.setAttribute("fill-opacity", this.getOpacity(x, y));
308354
cellRect.setAttribute('class', `blocklyLed${this.cellState[x][y] ? 'On' : 'Off'}`);
309-
cellRect.setAttribute("aria-checked", this.cellState[x][y].toString());
355+
cellRect.setAttribute("aria-checked", (!!this.cellState[x][y]).toString());
356+
if (this.isColorMatrix) {
357+
cellRect.setAttribute("aria-label", this.colorNames[this.cellState[x][y]] || this.palette[this.cellState[x][y]] || lf("color {0}", this.cellState[x][y]));
358+
}
310359
}
311360

312361
setValue(newValue: string | number, restoreState = true) {
@@ -357,12 +406,10 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
357406
const row = rows[y];
358407

359408
for (let j = 0; j < row.length && x < this.numMatrixCols; j++) {
360-
if (isNegativeCharacter(row[j])) {
361-
this.cellState[x][y] = false;
362-
x++;
363-
}
364-
else if (isPositiveCharacter(row[j])) {
365-
this.cellState[x][y] = true;
409+
const val = parseCharacter(row[j]);
410+
411+
if (val !== -1) {
412+
this.cellState[x][y] = val;
366413
x++;
367414
}
368415
}
@@ -372,12 +419,13 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
372419

373420
// Composes the state into a string an updates the field's state
374421
private updateValue() {
422+
const chars = ".#23456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
375423
let res = "";
376424
for (let y = 0; y < this.numMatrixRows; y++) {
377425
for (let x = 0; x < this.numMatrixCols; x++) {
378-
res += (this.cellState[x][y] ? "#" : ".") + " "
426+
res += chars.charAt(this.cellState[x][y]) + " ";
379427
}
380-
res += "\n" + FieldLedMatrix.TAB
428+
res += "\n" + FieldLedMatrix.TAB;
381429
}
382430

383431
// Blockly stores the state of the field as a string
@@ -393,12 +441,20 @@ export class FieldLedMatrix extends FieldMatrix implements FieldCustom {
393441
}
394442
}
395443

396-
function isPositiveCharacter(c: string) {
397-
return c === "#" || c === "*" || c === "1";
398-
}
399-
400-
function isNegativeCharacter(c: string) {
401-
return c === "." || c === "_" || c === "0";
444+
function parseCharacter(c: string): number {
445+
const chars = ".#23456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
446+
switch (c) {
447+
case "#":
448+
case "*":
449+
case "1":
450+
return 1;
451+
case ".":
452+
case "_":
453+
case "0":
454+
return 0;
455+
default:
456+
return chars.indexOf(c.toUpperCase());
457+
}
402458
}
403459

404460

0 commit comments

Comments
 (0)