2023-05-05 11:52:08 -07:00
|
|
|
import { clearScreen, fillRect } from "./window.ts";
|
2023-05-05 12:21:14 -07:00
|
|
|
import { fontWidth, fontHeight } from "./font.ts";
|
2023-05-05 11:52:08 -07:00
|
|
|
import { drawText } from "./builtins.ts";
|
|
|
|
import { COLOR } from "./colors.ts";
|
|
|
|
import {getSheet, setSheet} from "./sheet.ts";
|
2023-05-05 12:21:14 -07:00
|
|
|
import { K, getKeyboardString, keyPressed, shiftKeyDown } from "./keyboard.ts";
|
2023-05-04 20:27:01 -07:00
|
|
|
|
2023-05-05 11:52:08 -07:00
|
|
|
// deno-lint-ignore prefer-const
|
|
|
|
let tab: "code" | "sprite" | "map" | "sfx" | "music" = "code";
|
|
|
|
|
|
|
|
const codeTabState = {
|
|
|
|
scrollX: 0,
|
|
|
|
scrollY: 0,
|
|
|
|
anchor: 0,
|
|
|
|
focus: 0,
|
|
|
|
get focusX() {return indexToGrid(this.code, this.focus).x;},
|
|
|
|
get focusY() {return indexToGrid(this.code, this.focus).y;},
|
|
|
|
get anchorX() {return indexToGrid(this.code, this.anchor).x;},
|
|
|
|
get anchorY() {return indexToGrid(this.code, this.anchor).y;},
|
|
|
|
isCollapsed() {
|
|
|
|
return this.anchor === this.focus;
|
|
|
|
},
|
|
|
|
clampInRange(n: number) {
|
|
|
|
return Math.max(0, Math.min(n, this.code.length))
|
|
|
|
},
|
|
|
|
setSelection(anchor: number | {x: number, y: number}, focus?: number | {x: number, y: number}) {
|
|
|
|
if (typeof anchor !== "number") {
|
|
|
|
anchor = gridToIndex(this.code, anchor.x, anchor.y);
|
|
|
|
}
|
|
|
|
focus = focus ?? anchor;
|
|
|
|
if (typeof focus !== "number") {
|
|
|
|
focus = gridToIndex(this.code, focus.x, focus.y);
|
|
|
|
}
|
|
|
|
this.anchor = this.clampInRange(anchor),
|
|
|
|
this.focus = this.clampInRange(focus);
|
|
|
|
},
|
|
|
|
setFocus(focus: number | {x: number, y: number}) {
|
|
|
|
if (typeof focus !== "number") {
|
|
|
|
focus = gridToIndex(this.code, focus.x, focus.y);
|
|
|
|
}
|
|
|
|
this.focus = this.clampInRange(focus);
|
|
|
|
},
|
|
|
|
insertText(text: string) {
|
|
|
|
const {code, anchor, focus} = this;
|
|
|
|
this.code = code.slice(0, Math.min(anchor, focus)) + text + code.slice(Math.max(anchor, focus));
|
|
|
|
this.setSelection(Math.min(anchor, focus) + text.length);
|
|
|
|
},
|
2023-05-05 12:01:48 -07:00
|
|
|
indent(indentString: string) {
|
|
|
|
const lines = this.code.split("\n");
|
2023-05-05 12:05:02 -07:00
|
|
|
const {focusX, focusY, anchorX, anchorY} = this;
|
2023-05-05 12:01:48 -07:00
|
|
|
const newLines = lines.map((line, i) => {
|
|
|
|
if (i >= Math.min(focusY, anchorY) && i <= Math.max(focusY, anchorY)) {
|
|
|
|
return indentString+line;
|
|
|
|
} else {
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.code = newLines.join("\n");
|
2023-05-05 12:05:02 -07:00
|
|
|
this.setSelection({x: anchorX+1, y: anchorY}, {x: focusX+1, y: focusY});
|
2023-05-05 12:01:48 -07:00
|
|
|
},
|
|
|
|
outdent(outdentRegex: RegExp) {
|
|
|
|
const lines = this.code.split("\n");
|
2023-05-05 12:05:02 -07:00
|
|
|
const {focusX, focusY, anchorX, anchorY} = this;
|
2023-05-05 12:01:48 -07:00
|
|
|
const newLines = lines.map((line, i) => {
|
|
|
|
const match = line.match(outdentRegex);
|
|
|
|
if (i >= Math.min(focusY, anchorY) && i <= Math.max(focusY, anchorY) && match) {
|
|
|
|
return line.slice(match[0].length);
|
|
|
|
} else {
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.code = newLines.join("\n");
|
2023-05-05 12:05:02 -07:00
|
|
|
this.setSelection({x: Math.max(0,anchorX-1), y: anchorY}, {x: Math.max(0,focusX-1), y: focusY});
|
2023-05-05 12:01:48 -07:00
|
|
|
},
|
2023-05-05 11:52:08 -07:00
|
|
|
backspace() {
|
|
|
|
const {code, focus} = this;
|
|
|
|
if (this.isCollapsed()) {
|
|
|
|
if (focus > 0) {
|
|
|
|
this.code = code.slice(0, focus-1) + code.slice(focus);
|
|
|
|
this.setSelection(focus-1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.insertText("");
|
|
|
|
}
|
|
|
|
},
|
2023-05-05 12:10:01 -07:00
|
|
|
delete() {
|
|
|
|
const {code, focus} = this;
|
|
|
|
if (this.isCollapsed()) {
|
|
|
|
if (focus < code.length) {
|
|
|
|
this.code = code.slice(0, focus) + code.slice(1+focus);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.insertText("");
|
|
|
|
}
|
|
|
|
},
|
2023-05-05 11:52:08 -07:00
|
|
|
get code() {
|
|
|
|
return getSheet(0);
|
|
|
|
},
|
|
|
|
set code(val) {
|
|
|
|
setSheet(0, "code", val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const indexToGrid = (str: string, index: number) => {
|
|
|
|
const linesUpTo = str.slice(0,index).split("\n");
|
|
|
|
return {
|
|
|
|
x: linesUpTo[linesUpTo.length-1].length,
|
|
|
|
y: linesUpTo.length - 1,
|
|
|
|
}
|
|
|
|
}
|
2023-05-04 20:27:01 -07:00
|
|
|
|
2023-05-05 11:52:08 -07:00
|
|
|
const gridToIndex = (str: string, x: number, y: number) => {
|
|
|
|
const lines = str.split("\n");
|
|
|
|
if (y < 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (y >= lines.length) {
|
|
|
|
return str.length;
|
|
|
|
}
|
|
|
|
return lines.slice(0, y).join("\n").length+Math.min(x, lines[y].length)+1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const drawCodeField = (code: string, x: number, y: number, w: number, h: number) => {
|
|
|
|
const {
|
|
|
|
scrollX,
|
|
|
|
scrollY,
|
|
|
|
anchor,
|
|
|
|
focus,
|
|
|
|
} = codeTabState;
|
|
|
|
const {
|
|
|
|
x: focusX,
|
|
|
|
y: focusY,
|
|
|
|
} = indexToGrid(code, focus);
|
|
|
|
const {
|
|
|
|
x: anchorX,
|
|
|
|
y: anchorY,
|
|
|
|
} = indexToGrid(code, anchor);
|
|
|
|
fillRect(x, y, w, h, COLOR.DARKBLUE);
|
|
|
|
if (anchor === focus) {
|
|
|
|
fillRect(x+focusX*fontWidth-scrollX, y+focusY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.RED);
|
|
|
|
} else {
|
|
|
|
fillRect(x+anchorX*fontWidth-scrollX, y+anchorY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.GREEN);
|
|
|
|
fillRect(x+focusX*fontWidth-scrollX, y+focusY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.YELLOW);
|
|
|
|
}
|
|
|
|
code.split("\n").forEach((line, i) => {
|
|
|
|
drawText(x-scrollX, 1+y+i*(fontHeight+1)-scrollY, line);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const update = () => {
|
|
|
|
if (tab === "code") {
|
2023-05-05 12:21:14 -07:00
|
|
|
const { focus, focusX, focusY} = codeTabState;
|
2023-05-05 11:52:08 -07:00
|
|
|
const keyboardString = getKeyboardString();
|
|
|
|
if (keyboardString) {
|
|
|
|
codeTabState.insertText(keyboardString);
|
|
|
|
}
|
|
|
|
if (keyPressed(K.ENTER)) {
|
|
|
|
codeTabState.insertText("\n");
|
|
|
|
}
|
|
|
|
if (keyPressed(K.TAB)) {
|
|
|
|
if (!shiftKeyDown()) {
|
|
|
|
if (codeTabState.isCollapsed()) {
|
2023-05-05 12:01:48 -07:00
|
|
|
codeTabState.insertText("\t");
|
2023-05-05 11:52:08 -07:00
|
|
|
} else {
|
2023-05-05 12:01:48 -07:00
|
|
|
codeTabState.indent("\t");
|
2023-05-05 11:52:08 -07:00
|
|
|
}
|
|
|
|
} else {
|
2023-05-05 12:01:48 -07:00
|
|
|
codeTabState.outdent(/^(\t| )/);
|
2023-05-05 11:52:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (keyPressed(K.BACKSPACE)) {
|
|
|
|
codeTabState.backspace();
|
|
|
|
}
|
2023-05-05 12:10:01 -07:00
|
|
|
if (keyPressed(K.DELETE)) {
|
|
|
|
codeTabState.delete();
|
|
|
|
}
|
2023-05-05 11:52:08 -07:00
|
|
|
if (keyPressed(K.ARROW_RIGHT)) {
|
|
|
|
if (shiftKeyDown()) {
|
|
|
|
codeTabState.setFocus(focus+1);
|
|
|
|
} else {
|
|
|
|
codeTabState.setSelection(focus+1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (keyPressed(K.ARROW_LEFT)) {
|
|
|
|
if (shiftKeyDown()) {
|
|
|
|
codeTabState.setFocus(focus-1);
|
|
|
|
} else {
|
|
|
|
codeTabState.setSelection(focus-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (keyPressed(K.ARROW_DOWN)) {
|
|
|
|
if (shiftKeyDown()) {
|
|
|
|
codeTabState.setFocus({x: focusX, y: focusY+1});
|
|
|
|
} else {
|
|
|
|
codeTabState.setSelection({x: focusX, y: focusY+1});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (keyPressed(K.ARROW_UP)) {
|
|
|
|
if (shiftKeyDown()) {
|
|
|
|
codeTabState.setFocus({x: focusX, y: focusY-1});
|
|
|
|
} else {
|
|
|
|
codeTabState.setSelection({x: focusX, y: focusY-1});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-04 20:27:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const draw = () => {
|
2023-05-05 11:52:08 -07:00
|
|
|
clearScreen();
|
|
|
|
if (tab === "code") {
|
|
|
|
drawCodeField(getSheet(0), 0, 8, 128, 112);
|
|
|
|
}
|
2023-05-04 20:27:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export const editmode = {
|
|
|
|
update,
|
|
|
|
draw,
|
|
|
|
}
|