225 lines
7.0 KiB
TypeScript
225 lines
7.0 KiB
TypeScript
/// <reference path="../node_modules/pxt-core/localtypings/blockly.d.ts" />
|
|
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
|
|
|
const rowRegex = /^.*[\.#].*$/;
|
|
|
|
enum LabelMode {
|
|
None,
|
|
Number,
|
|
Letter
|
|
}
|
|
|
|
export class FieldMatrix extends Blockly.Field implements Blockly.FieldCustom {
|
|
private static CELL_WIDTH = 23;
|
|
private static CELL_HORIZONTAL_MARGIN = 7;
|
|
private static CELL_VERTICAL_MARGIN = 5;
|
|
private static CELL_CORNER_RADIUS = 5;
|
|
private static BOTTOM_MARGIN = 9;
|
|
private static Y_AXIS_WIDTH = 9;
|
|
private static X_AXIS_HEIGHT = 10;
|
|
private static TAB = " ";
|
|
|
|
public isFieldCustom_ = true;
|
|
|
|
private params: any;
|
|
private onColor = "#e2484a";
|
|
private offColor = "white";
|
|
|
|
// The number of columns
|
|
private matrixWidth: number = 5;
|
|
|
|
// The number of rows
|
|
private matrixHeight: number = 5;
|
|
|
|
private yAxisLabel: LabelMode = LabelMode.None;
|
|
private xAxisLabel: LabelMode = LabelMode.None;
|
|
|
|
private cellState: boolean[][] = [];
|
|
private elt: SVGSVGElement;
|
|
|
|
constructor(text: string, params: any, validator?: Function) {
|
|
super(text, validator);
|
|
this.params = params;
|
|
|
|
if (this.params.rows !== undefined) {
|
|
let val = parseInt(this.params.rows);
|
|
if (!isNaN(val)) {
|
|
this.matrixHeight = val;
|
|
}
|
|
}
|
|
|
|
if (this.params.columns !== undefined) {
|
|
let val = parseInt(this.params.columns);
|
|
if (!isNaN(val)) {
|
|
this.matrixWidth = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show the inline free-text editor on top of the text.
|
|
* @private
|
|
*/
|
|
showEditor_() {
|
|
// Intentionally left empty
|
|
}
|
|
|
|
private initMatrix() {
|
|
this.elt = pxsim.svg.parseString(`<svg xmlns="http://www.w3.org/2000/svg" id="field-matrix" />`);
|
|
|
|
// Initialize the matrix that holds the state
|
|
for (let i = 0; i < this.matrixHeight; i++) {
|
|
this.cellState.push([])
|
|
for (let j = 0; j < this.matrixWidth; j++) {
|
|
this.cellState[i].push(false);
|
|
}
|
|
}
|
|
|
|
this.restoreStateFromString();
|
|
this.updateValue()
|
|
|
|
// Create the cells of the matrix that is displayed
|
|
for (let i = 0; i < this.matrixWidth; i++) {
|
|
for (let j = 0; j < this.matrixHeight; j++) {
|
|
this.createCell(i, j);
|
|
}
|
|
}
|
|
|
|
if (this.xAxisLabel !== LabelMode.None) {
|
|
const y = this.matrixHeight * (FieldMatrix.CELL_WIDTH + FieldMatrix.CELL_VERTICAL_MARGIN) + FieldMatrix.CELL_VERTICAL_MARGIN * 2 + FieldMatrix.BOTTOM_MARGIN
|
|
const xAxis = pxsim.svg.child(this.elt, "g", { transform: `translate(${0} ${y})` });
|
|
for (let i = 0; i < this.matrixWidth; i++) {
|
|
const x = this.getYAxisWidth() + i * (FieldMatrix.CELL_WIDTH + FieldMatrix.CELL_HORIZONTAL_MARGIN) + FieldMatrix.CELL_WIDTH / 2 + FieldMatrix.CELL_HORIZONTAL_MARGIN / 2;
|
|
const lbl = pxsim.svg.child(xAxis, "text", { x, class: "blocklyText" })
|
|
lbl.textContent = this.getLabel(i, this.xAxisLabel);
|
|
}
|
|
}
|
|
|
|
if (this.yAxisLabel !== LabelMode.None) {
|
|
const yAxis = pxsim.svg.child(this.elt, "g", {});
|
|
for (let i = 0; i < this.matrixHeight; i++) {
|
|
const y = i * (FieldMatrix.CELL_WIDTH + FieldMatrix.CELL_VERTICAL_MARGIN) + FieldMatrix.CELL_WIDTH / 2 + FieldMatrix.CELL_VERTICAL_MARGIN * 2;
|
|
const lbl = pxsim.svg.child(yAxis, "text", { x: 0, y, class: "blocklyText" })
|
|
lbl.textContent = this.getLabel(i, this.yAxisLabel);
|
|
}
|
|
}
|
|
|
|
this.fieldGroup_.appendChild(this.elt);
|
|
}
|
|
|
|
private getLabel(index: number, mode: LabelMode) {
|
|
switch (mode) {
|
|
case LabelMode.Letter:
|
|
return String.fromCharCode(index + /*char code for A*/ 65);
|
|
default:
|
|
return (index + 1).toString();
|
|
}
|
|
}
|
|
|
|
private createCell(x: number, y: number) {
|
|
const tx = x * (FieldMatrix.CELL_WIDTH + FieldMatrix.CELL_HORIZONTAL_MARGIN) + FieldMatrix.CELL_HORIZONTAL_MARGIN + this.getYAxisWidth();
|
|
const ty = y * (FieldMatrix.CELL_WIDTH + FieldMatrix.CELL_VERTICAL_MARGIN) + FieldMatrix.CELL_VERTICAL_MARGIN;
|
|
|
|
const cellG = pxsim.svg.child(this.elt, "g", { transform: `translate(${tx} ${ty})` }) as SVGGElement;
|
|
const cellRect = pxsim.svg.child(cellG, "rect", { width: FieldMatrix.CELL_WIDTH, height: FieldMatrix.CELL_WIDTH, fill: this.getColor(x, y), rx: FieldMatrix.CELL_CORNER_RADIUS }) as SVGRectElement;
|
|
|
|
pxsim.svg.onClick(cellRect, () => {
|
|
this.cellState[x][y] = !this.cellState[x][y];
|
|
cellRect.setAttribute("fill", this.getColor(x, y));
|
|
this.updateValue()
|
|
});
|
|
}
|
|
|
|
private getColor(x: number, y: number) {
|
|
return this.cellState[x][y] ? this.onColor : this.offColor;
|
|
}
|
|
|
|
render_() {
|
|
if (!this.visible_) {
|
|
this.size_.width = 0;
|
|
return;
|
|
}
|
|
|
|
if (!this.elt) {
|
|
this.initMatrix();
|
|
}
|
|
|
|
|
|
// The height and width must be set by the render function
|
|
this.size_.height = Number(this.matrixHeight) * (FieldMatrix.CELL_WIDTH + FieldMatrix.CELL_VERTICAL_MARGIN) + FieldMatrix.CELL_VERTICAL_MARGIN * 2 + FieldMatrix.BOTTOM_MARGIN + this.getXAxisHeight()
|
|
this.size_.width = Number(this.matrixWidth) * (FieldMatrix.CELL_WIDTH + FieldMatrix.CELL_HORIZONTAL_MARGIN) + this.getYAxisWidth();
|
|
}
|
|
|
|
// The return value of this function is inserted in the code
|
|
getValue() {
|
|
// getText() returns the value that is set by calls to setValue()
|
|
let text = removeQuotes(this.getText());
|
|
return `\`\n${FieldMatrix.TAB}${text}\n${FieldMatrix.TAB}\``;
|
|
}
|
|
|
|
// Restores the block state from the text value of the field
|
|
private restoreStateFromString() {
|
|
let r = this.getText();
|
|
if (r) {
|
|
const rows = r.split("\n").filter(r => rowRegex.test(r));
|
|
|
|
for (let y = 0; y < rows.length && y < this.matrixHeight; y++) {
|
|
let x = 0;
|
|
const row = rows[y];
|
|
|
|
for (let j = 0; j < row.length && x < this.matrixWidth; j++) {
|
|
if (isNegativeCharacter(row[j])) {
|
|
this.cellState[x][y] = false;
|
|
x++;
|
|
}
|
|
else if (isPositiveCharacter(row[j])) {
|
|
this.cellState[x][y] = true;
|
|
x++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Composes the state into a string an updates the field's state
|
|
private updateValue() {
|
|
let res = "";
|
|
for (let y = 0; y < this.matrixHeight; y++) {
|
|
for (let x = 0; x < this.matrixWidth; x++) {
|
|
res += (this.cellState[x][y] ? "#" : ".") + " "
|
|
}
|
|
res += "\n" + FieldMatrix.TAB
|
|
}
|
|
|
|
// Blockly stores the state of the field as a string
|
|
this.setValue(res);
|
|
}
|
|
|
|
private getYAxisWidth() {
|
|
return this.yAxisLabel === LabelMode.None ? 0 : FieldMatrix.Y_AXIS_WIDTH;
|
|
}
|
|
|
|
private getXAxisHeight() {
|
|
return this.xAxisLabel === LabelMode.None ? 0 : FieldMatrix.X_AXIS_HEIGHT;
|
|
}
|
|
}
|
|
|
|
function isPositiveCharacter(c: string) {
|
|
return c === "#" || c === "*" || c === "1";
|
|
}
|
|
|
|
function isNegativeCharacter(c: string) {
|
|
return c === "." || c === "_" || c === "0";
|
|
}
|
|
|
|
|
|
const allQuotes = ["'", '"', "`"];
|
|
|
|
function removeQuotes(str: string) {
|
|
str = str.trim();
|
|
const start = str.charAt(0);
|
|
if (start === str.charAt(str.length - 1) && allQuotes.indexOf(start) !== -1) {
|
|
return str.substr(1, str.length - 2).trim();
|
|
}
|
|
return str;
|
|
} |