Reorganize
This commit is contained in:
694
editor/codetab.ts
Normal file
694
editor/codetab.ts
Normal file
@ -0,0 +1,694 @@
|
||||
import { clearScreen, fillRect } from "../io/window.ts";
|
||||
import { CHAR, font } from "../data/font.ts";
|
||||
import { drawText, measureText } from "../runtime/builtins.ts";
|
||||
import { COLOR } from "../data/colors.ts";
|
||||
import { getCodeSheet, setSheet } from "../io/sheet.ts";
|
||||
import { K, ctrlKeyDown, getKeyboardString, keyPressed, shiftKeyDown } from "../io/keyboard.ts";
|
||||
import { clipboard, tokenize } from "../deps.ts";
|
||||
import { getBuiltins } from "../runtime/runcode.ts";
|
||||
import { page } from "./viewsheets.ts";
|
||||
import { mouseDown, mouseHeld, mousePos } from "../io/mouse.ts";
|
||||
|
||||
const historyDebounceFrames = 20;
|
||||
|
||||
const fontHeight = font.height;
|
||||
|
||||
const keywords = [
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"class",
|
||||
"const",
|
||||
"continue",
|
||||
"debugger",
|
||||
"default",
|
||||
"delete",
|
||||
"do",
|
||||
"else",
|
||||
"export",
|
||||
"extends",
|
||||
"finally",
|
||||
"for",
|
||||
"function",
|
||||
"if",
|
||||
"import",
|
||||
"in",
|
||||
"instanceof",
|
||||
"new",
|
||||
"return",
|
||||
"super",
|
||||
"switch",
|
||||
"this",
|
||||
"throw",
|
||||
"try",
|
||||
"typeof",
|
||||
"var",
|
||||
"void",
|
||||
"while",
|
||||
"with",
|
||||
"let",
|
||||
"static",
|
||||
"yield",
|
||||
"await",
|
||||
"enum",
|
||||
"implements",
|
||||
"interface",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"=>",
|
||||
];
|
||||
const values = [
|
||||
"false",
|
||||
"null",
|
||||
"true",
|
||||
"undefined",
|
||||
"NaN",
|
||||
"Infinity",
|
||||
CHAR.PI,
|
||||
];
|
||||
const operator = [
|
||||
"&&",
|
||||
"||",
|
||||
"??",
|
||||
"--",
|
||||
"++",
|
||||
".",
|
||||
"?.",
|
||||
"<",
|
||||
"<=",
|
||||
">",
|
||||
">=",
|
||||
"!=",
|
||||
"!==",
|
||||
"==",
|
||||
"===",
|
||||
"+",
|
||||
"-",
|
||||
"%",
|
||||
"&",
|
||||
"|",
|
||||
"^",
|
||||
"/",
|
||||
"*",
|
||||
"**",
|
||||
"<<",
|
||||
">>",
|
||||
">>>",
|
||||
"=",
|
||||
"+=",
|
||||
"-=",
|
||||
"%=",
|
||||
"&=",
|
||||
"|=",
|
||||
"^=",
|
||||
"/=",
|
||||
"*=",
|
||||
"**=",
|
||||
"<<=",
|
||||
">>=",
|
||||
">>>=",
|
||||
"!",
|
||||
"?",
|
||||
"~",
|
||||
"...",
|
||||
];
|
||||
const punctuation = [
|
||||
"(",
|
||||
")",
|
||||
"[",
|
||||
"]",
|
||||
"{",
|
||||
"}",
|
||||
".",
|
||||
":",
|
||||
";",
|
||||
",",
|
||||
];
|
||||
|
||||
const builtinColor = COLOR.BLUE;
|
||||
const keywordColor = COLOR.PURPLE;
|
||||
const operatorColor = COLOR.CYAN;
|
||||
const valueColor = COLOR.ORANGE;
|
||||
const stringColor = COLOR.GREEN;
|
||||
const regexColor = COLOR.PINK;
|
||||
const punctuationColor = COLOR.LIGHTGRAY;
|
||||
const commentColor = COLOR.DARKGREEN;
|
||||
const identifierColor = COLOR.YELLOW;
|
||||
const invalidColor = COLOR.RED;
|
||||
|
||||
const caretColor = COLOR.WHITE;
|
||||
const selectionColor = COLOR.DARKBLUE;
|
||||
|
||||
const backgroundColor = COLOR.DARKERBLUE;
|
||||
|
||||
const tokenColors = {
|
||||
"StringLiteral": stringColor,
|
||||
"NoSubstitutionTemplate": stringColor,
|
||||
"TemplateHead": stringColor,
|
||||
"TemplateMiddle": stringColor,
|
||||
"TemplateTail": stringColor,
|
||||
"RegularExpressionLiteral": regexColor,
|
||||
"MultiLineComment": commentColor,
|
||||
"SingleLineComment": commentColor,
|
||||
"IdentifierName": identifierColor,
|
||||
"PrivateIdentifier": identifierColor,
|
||||
"NumericLiteral": valueColor,
|
||||
"Punctuator": punctuationColor,
|
||||
"WhiteSpace": punctuationColor,
|
||||
"LineTerminatorSequence": punctuationColor,
|
||||
"Invalid": invalidColor,
|
||||
}
|
||||
|
||||
const transformForCopy = (text: string) => {
|
||||
text = text.replaceAll(CHAR.UP, "⬆️");
|
||||
text = text.replaceAll(CHAR.LEFT, "⬅️");
|
||||
text = text.replaceAll(CHAR.DOWN, "⬇️");
|
||||
text = text.replaceAll(CHAR.RIGHT, "➡️");
|
||||
return text;
|
||||
}
|
||||
|
||||
const transformForPaste = (text: string) => {
|
||||
let newstr = "";
|
||||
text = text.replaceAll("⬆️", CHAR.UP);
|
||||
text = text.replaceAll("⬅️", CHAR.LEFT);
|
||||
text = text.replaceAll("⬇️", CHAR.DOWN);
|
||||
text = text.replaceAll("➡️", CHAR.RIGHT);
|
||||
for (const char of text) {
|
||||
if (char in font.chars) {
|
||||
newstr += char;
|
||||
}
|
||||
}
|
||||
return newstr;
|
||||
}
|
||||
|
||||
const state = {
|
||||
doubleClickTimer: 0,
|
||||
history: [] as Array<{code: string, anchor: number, focus: number}>,
|
||||
historyDebounce: 0,
|
||||
historyIndex: 0,
|
||||
undo() {
|
||||
console.log('undoing');
|
||||
if (this.historyIndex === this.history.length && this.historyDebounce > 0) {
|
||||
this.snapshot();
|
||||
}
|
||||
console.log('historyIndex', this.historyIndex);
|
||||
if (this.historyIndex > 0) {
|
||||
this.historyIndex -= 1;
|
||||
const snap = this.history[this.historyIndex];
|
||||
console.log('historyIndex', this.historyIndex);
|
||||
this.code = snap.code;
|
||||
this.setSelection(snap.anchor, snap.focus);
|
||||
}
|
||||
},
|
||||
redo() {
|
||||
console.log('redoing');
|
||||
if (this.historyIndex < this.history.length-1) {
|
||||
this.historyIndex += 1;
|
||||
const snap = this.history[this.historyIndex];
|
||||
this.code = snap.code;
|
||||
this.setSelection(snap.anchor, snap.focus);
|
||||
}
|
||||
},
|
||||
snapshot() {
|
||||
const snap = {
|
||||
code: this.code,
|
||||
anchor: this.anchor,
|
||||
focus: this.focus,
|
||||
};
|
||||
this.history.push(snap);
|
||||
console.log('took snapshot', this.historyIndex, snap);
|
||||
},
|
||||
startSnapping() {
|
||||
console.log('start snapping', this.historyIndex);
|
||||
if (this.historyDebounce <= 0) {
|
||||
this.historyIndex += 1;
|
||||
}
|
||||
if (this.history.length > this.historyIndex) {
|
||||
this.history.length = this.historyIndex;
|
||||
}
|
||||
this.historyDebounce = historyDebounceFrames;
|
||||
},
|
||||
wordMode: false,
|
||||
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;},
|
||||
get focusPixelX() {return indexToRect(this.code, this.focus).x;},
|
||||
get focusPixelY() {return indexToRect(this.code, this.focus).y;},
|
||||
get anchorPixelX() {return indexToRect(this.code, this.anchor).x;},
|
||||
get anchorPixelY() {return indexToRect(this.code, this.anchor).y;},
|
||||
isCollapsed() {
|
||||
return this.anchor === this.focus;
|
||||
},
|
||||
clampInRange(n: number) {
|
||||
return Math.max(0, Math.min(n, this.code.length))
|
||||
},
|
||||
findNearestWordBoundaryLeft(index: number) {
|
||||
if (index === this.code.length-1) {
|
||||
return index;
|
||||
}
|
||||
const words1 = this.code.slice(0, index+1).split(/\b/g);
|
||||
if (words1[words1.length-1].length === 1) {
|
||||
return index;
|
||||
}
|
||||
const words = this.code.slice(0, index).split(/\b/g);
|
||||
if (!words.length) {
|
||||
return 0;
|
||||
}
|
||||
return index-words[words.length-1].length;
|
||||
},
|
||||
findNearestWordBoundaryRight(index: number) {
|
||||
if (index === 0) {
|
||||
return index;
|
||||
}
|
||||
const words1 = this.code.slice(index-1).split(/\b/g);
|
||||
if (words1[0].length === 1) {
|
||||
return index;
|
||||
}
|
||||
const words = this.code.slice(index).split(/\b/g);
|
||||
if (!words.length) {
|
||||
return this.code.length;
|
||||
}
|
||||
return index+words[0].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);
|
||||
if (this.wordMode) {
|
||||
console.log('word mode', this.anchor, this.focus, this.findNearestWordBoundaryLeft(this.anchor), this.findNearestWordBoundaryRight(this.focus));
|
||||
if (this.anchor <= this.focus) {
|
||||
this.anchor = this.findNearestWordBoundaryLeft(this.anchor);
|
||||
this.focus = this.findNearestWordBoundaryRight(this.focus);
|
||||
} else {
|
||||
this.anchor = this.findNearestWordBoundaryRight(this.anchor);
|
||||
this.focus = this.findNearestWordBoundaryLeft(this.focus);
|
||||
}
|
||||
}
|
||||
this.anchor = this.clampInRange(this.anchor);
|
||||
this.focus = this.clampInRange(this.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);
|
||||
if (this.wordMode) {
|
||||
if (this.anchor <= this.focus) {
|
||||
this.anchor = this.findNearestWordBoundaryLeft(this.anchor);
|
||||
this.focus = this.findNearestWordBoundaryRight(this.focus);
|
||||
} else {
|
||||
this.anchor = this.findNearestWordBoundaryRight(this.anchor);
|
||||
this.focus = this.findNearestWordBoundaryLeft(this.focus);
|
||||
}
|
||||
}
|
||||
this.focus = this.clampInRange(this.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);
|
||||
this.startSnapping();
|
||||
},
|
||||
toggleComment() {
|
||||
const lines = this.code.split("\n");
|
||||
const {focusX, focusY, anchorX, anchorY} = this;
|
||||
const lineInSelection = (i: number) => i >= Math.min(focusY, anchorY) && i <= Math.max(focusY, anchorY);
|
||||
const allLinesAreCommented = lines.every((line, i) => {
|
||||
if (lineInSelection(i) && !line.trim().startsWith("// ")) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
const newLines = lines.map((line, i) => {
|
||||
if (lineInSelection(i)) {
|
||||
if (allLinesAreCommented) {
|
||||
return line.slice(3);
|
||||
} else {
|
||||
return "// "+line;
|
||||
}
|
||||
} else {
|
||||
return line;
|
||||
}
|
||||
});
|
||||
this.code = newLines.join("\n");
|
||||
const shiftBy = allLinesAreCommented ? -3 : 3;
|
||||
this.setSelection({x: anchorX+shiftBy, y: anchorY}, {x: focusX+shiftBy, y: focusY});
|
||||
this.startSnapping();
|
||||
},
|
||||
indent(indentString: string) {
|
||||
const lines = this.code.split("\n");
|
||||
const {focusX, focusY, anchorX, anchorY} = this;
|
||||
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");
|
||||
this.setSelection({x: anchorX+1, y: anchorY}, {x: focusX+1, y: focusY});
|
||||
this.startSnapping();
|
||||
},
|
||||
outdent(outdentRegex: RegExp) {
|
||||
const lines = this.code.split("\n");
|
||||
const {focusX, focusY, anchorX, anchorY} = this;
|
||||
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");
|
||||
this.setSelection({x: Math.max(0,anchorX-1), y: anchorY}, {x: Math.max(0,focusX-1), y: focusY});
|
||||
this.startSnapping();
|
||||
},
|
||||
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);
|
||||
this.startSnapping();
|
||||
}
|
||||
} else {
|
||||
this.insertText("");
|
||||
}
|
||||
},
|
||||
delete() {
|
||||
const {code, focus} = this;
|
||||
if (this.isCollapsed()) {
|
||||
if (focus < code.length) {
|
||||
this.code = code.slice(0, focus) + code.slice(1+focus);
|
||||
this.startSnapping();
|
||||
}
|
||||
} else {
|
||||
this.insertText("");
|
||||
}
|
||||
},
|
||||
async copy() {
|
||||
const {code, anchor, focus} = this;
|
||||
const selected = code.slice(Math.min(anchor,focus), Math.max(anchor,focus));
|
||||
await clipboard.writeText(transformForCopy(selected));
|
||||
},
|
||||
async cut() {
|
||||
await this.copy();
|
||||
this.insertText("");
|
||||
},
|
||||
async paste() {
|
||||
this.insertText(transformForPaste(await clipboard.readText()));
|
||||
},
|
||||
scrollToCursor() {
|
||||
const {focusY, scrollY, scrollX, focus} = this;
|
||||
const fh = fontHeight + 1;
|
||||
const rect = indexToRect(this.code, focus);
|
||||
if (focusY*fh < scrollY) {
|
||||
this.scrollY = focusY*fh;
|
||||
}
|
||||
if (focusY*fh > scrollY+112-fh) {
|
||||
this.scrollY = focusY*fh-112+fh;
|
||||
}
|
||||
if (rect.x < scrollX) {
|
||||
this.scrollX = rect.x;
|
||||
}
|
||||
if (rect.x+rect.w > scrollX+128) {
|
||||
this.scrollX = rect.x-128+rect.w+1;
|
||||
}
|
||||
},
|
||||
currentIndentation() {
|
||||
const lines = this.code.slice(0, this.focus).split("\n");
|
||||
const line = lines[lines.length-1];
|
||||
const match = line.match(/^\s*/);
|
||||
if (!match) {
|
||||
return "";
|
||||
}
|
||||
return match[0];
|
||||
},
|
||||
get code() {
|
||||
return getCodeSheet(page.activeSheet);
|
||||
},
|
||||
set code(val) {
|
||||
setSheet(page.activeSheet, "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,
|
||||
}
|
||||
}
|
||||
|
||||
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)+(y === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
const indexToRect = (str: string, index: number) => {
|
||||
const linesUpTo = str.slice(0,index).split("\n");
|
||||
let extra = 0;
|
||||
if (linesUpTo[linesUpTo.length-1].length > 0) {
|
||||
extra = 1;
|
||||
}
|
||||
return {
|
||||
x: measureText(linesUpTo[linesUpTo.length-1]) + extra,
|
||||
y: (fontHeight + 1)*(linesUpTo.length - 1),
|
||||
w: measureText(str[index] ?? "\n"),
|
||||
h: fontHeight+1,
|
||||
}
|
||||
}
|
||||
|
||||
const pixelToIndex = (str: string, x: number, y: number) => {
|
||||
const lines = str.split("\n");
|
||||
if (y < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (y >= (fontHeight+1)*lines.length) {
|
||||
return str.length;
|
||||
}
|
||||
const yy = Math.floor(y/(fontHeight+1));
|
||||
const prefix = lines.slice(0, yy).join("\n").length+(yy === 0 ? 0 : 1);
|
||||
const line = lines[yy];
|
||||
let j = 0;
|
||||
while (measureText(line.slice(0, j)) < x && j < line.length) {
|
||||
j+=1;
|
||||
}
|
||||
if (measureText(line) < x) {
|
||||
j+=1;
|
||||
}
|
||||
return prefix + Math.max(0, j-1);
|
||||
}
|
||||
|
||||
const update = async () => {
|
||||
const { focus } = state;
|
||||
if (state.history.length === 0) {
|
||||
state.snapshot();
|
||||
}
|
||||
if (state.historyDebounce > 0) {
|
||||
state.historyDebounce -= 1;
|
||||
if (state.historyDebounce <= 0) {
|
||||
state.snapshot();
|
||||
}
|
||||
}
|
||||
if (state.doubleClickTimer > 0) {
|
||||
state.doubleClickTimer -= 1;
|
||||
}
|
||||
|
||||
if (mouseDown() && !shiftKeyDown()) {
|
||||
if (state.doubleClickTimer > 0) {
|
||||
state.wordMode = true;
|
||||
} else {
|
||||
state.doubleClickTimer = 10;
|
||||
}
|
||||
const {x, y} = mousePos();
|
||||
state.setSelection(pixelToIndex(state.code, x+state.scrollX, y+state.scrollY-8));
|
||||
state.scrollToCursor();
|
||||
} else if (mouseHeld()) {
|
||||
const {x, y} = mousePos();
|
||||
state.setFocus(pixelToIndex(state.code, x+state.scrollX, y+state.scrollY-8));
|
||||
state.scrollToCursor();
|
||||
} else {
|
||||
state.wordMode = false;
|
||||
}
|
||||
|
||||
const keyboardString = getKeyboardString();
|
||||
if (keyboardString) {
|
||||
state.insertText(keyboardString);
|
||||
state.scrollToCursor();
|
||||
}
|
||||
|
||||
if (keyPressed(K.ENTER)) {
|
||||
state.insertText("\n"+state.currentIndentation());
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed(K.TAB)) {
|
||||
if (!shiftKeyDown()) {
|
||||
if (state.isCollapsed()) {
|
||||
state.insertText("\t");
|
||||
} else {
|
||||
state.indent("\t");
|
||||
}
|
||||
} else {
|
||||
state.outdent(/^(\t| )/);
|
||||
}
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed(K.BACKSPACE)) {
|
||||
state.backspace();
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed(K.DELETE)) {
|
||||
state.delete();
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed(K.ARROW_RIGHT)) {
|
||||
if (shiftKeyDown()) {
|
||||
state.setFocus(focus+1);
|
||||
} else {
|
||||
state.setSelection(focus+1);
|
||||
}
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed(K.ARROW_LEFT)) {
|
||||
if (shiftKeyDown()) {
|
||||
state.setFocus(focus-1);
|
||||
} else {
|
||||
state.setSelection(focus-1);
|
||||
}
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed(K.ARROW_DOWN)) {
|
||||
const rect = indexToRect(state.code, focus);
|
||||
const newIndex = pixelToIndex(state.code, rect.x, rect.y+rect.h+1+1);
|
||||
if (shiftKeyDown()) {
|
||||
state.setFocus(newIndex);
|
||||
} else {
|
||||
state.setSelection(newIndex);
|
||||
}
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed(K.ARROW_UP)) {
|
||||
const rect = indexToRect(state.code, focus);
|
||||
const newIndex = pixelToIndex(state.code, rect.x, rect.y-1-1);
|
||||
if (shiftKeyDown()) {
|
||||
state.setFocus(newIndex);
|
||||
} else {
|
||||
state.setSelection(newIndex);
|
||||
}
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed("C") && ctrlKeyDown()) {
|
||||
await state.copy();
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed("X") && ctrlKeyDown()) {
|
||||
await state.cut();
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed("V") && ctrlKeyDown()) {
|
||||
await state.paste();
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed("Z") && ctrlKeyDown()) {
|
||||
if (shiftKeyDown()) {
|
||||
state.redo();
|
||||
} else {
|
||||
state.undo();
|
||||
}
|
||||
}
|
||||
if (keyPressed("Y") && ctrlKeyDown()) {
|
||||
state.redo();
|
||||
}
|
||||
if (keyPressed("/") && ctrlKeyDown()) {
|
||||
state.toggleComment();
|
||||
}
|
||||
}
|
||||
|
||||
const draw = () => {
|
||||
clearScreen();
|
||||
const {
|
||||
scrollX,
|
||||
scrollY,
|
||||
anchor,
|
||||
focus,
|
||||
code,
|
||||
} = state;
|
||||
const x = 0;
|
||||
const y = 8;
|
||||
const w = 128;
|
||||
const h = 112;
|
||||
fillRect(x, y, w, h, backgroundColor);
|
||||
if (anchor !== focus) {
|
||||
for (let i = Math.min(anchor, focus); i < Math.max(anchor, focus); i++) {
|
||||
const sel = indexToRect(code, i);
|
||||
fillRect(x+sel.x-scrollX, y+sel.y-scrollY, sel.w+2, sel.h, selectionColor);
|
||||
}
|
||||
}
|
||||
const rect = indexToRect(code, focus);
|
||||
fillRect(x+rect.x-scrollX, y+rect.y-scrollY, 1, rect.h, caretColor);
|
||||
|
||||
const builtins = Object.keys(getBuiltins());
|
||||
const tokens = [...tokenize(code)];
|
||||
let cx = 0;
|
||||
let cy = 0;
|
||||
tokens.forEach((token) => {
|
||||
if (token.type === "LineTerminatorSequence") {
|
||||
cx=0;
|
||||
cy+=fontHeight+1;
|
||||
return;
|
||||
}
|
||||
const lines = token.value.split("\n");
|
||||
lines.forEach((line, i) => {
|
||||
let color = tokenColors[token.type];
|
||||
if (builtins.includes(token.value)) {
|
||||
color = builtinColor;
|
||||
}
|
||||
if (keywords.includes(token.value)) {
|
||||
color = keywordColor;
|
||||
}
|
||||
if (values.includes(token.value)) {
|
||||
color = valueColor;
|
||||
}
|
||||
if (operator.includes(token.value)) {
|
||||
color = operatorColor;
|
||||
}
|
||||
if (punctuation.includes(token.value)) {
|
||||
color = punctuationColor;
|
||||
}
|
||||
drawText(1+x+cx-scrollX, 1+y+cy-scrollY, line, color);
|
||||
if (i === lines.length-1) {
|
||||
cx += measureText(line)+1;
|
||||
} else {
|
||||
cx=0;
|
||||
cy+=fontHeight+1;
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export const codetab = {
|
||||
update,
|
||||
draw,
|
||||
}
|
95
editor/editmode.ts
Normal file
95
editor/editmode.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { clearScreen, fillRect } from "../io/window.ts";
|
||||
import { codetab } from "./codetab.ts";
|
||||
import { spritetab } from "./spritetab.ts";
|
||||
import { viewsheets, page } from "./viewsheets.ts";
|
||||
import { COLOR } from "../data/colors.ts";
|
||||
import { mouseClick, mousePos } from "../io/mouse.ts";
|
||||
import { drawIcon } from "../runtime/builtins.ts";
|
||||
import { inRect } from "../util/util.ts";
|
||||
import { sheetsIcon, trashIcon } from "../data/icons.ts";
|
||||
import { SheetType, setSheet } from "../io/sheet.ts";
|
||||
import { nonetab } from "./nonetab.ts";
|
||||
import { maptab } from "./maptab.ts";
|
||||
|
||||
type TabName = SheetType; // "code" | "sprite" | "map" | "sfx" | "music" | "sheet";
|
||||
|
||||
const buttons: Array<{update: () => void, draw: () => void}> = [];
|
||||
const makeTabButton = (tabname: TabName | "sheet", x: number, y: number, icon: Array<number>) => {
|
||||
buttons.push({
|
||||
update() {
|
||||
if (mouseClick()) {
|
||||
const {x: mouseX, y: mouseY} = mousePos();
|
||||
if (inRect(mouseX, mouseY, x, y, 8, 8)) {
|
||||
page.tab = tabname;
|
||||
}
|
||||
}
|
||||
},
|
||||
draw() {
|
||||
drawIcon(x, y, icon, page.tab === tabname ? COLOR.YELLOW : COLOR.WHITE);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const makeTrashButton = (x: number, y: number, icon: Array<number>) => {
|
||||
buttons.push({
|
||||
update() {
|
||||
if (page.tab !== "sheet") {
|
||||
return
|
||||
}
|
||||
if (mouseClick()) {
|
||||
const {x: mouseX, y: mouseY} = mousePos();
|
||||
if (inRect(mouseX, mouseY, x, y, 8, 8)) {
|
||||
setSheet(page.activeSheet, "none", null);
|
||||
page.tab = "sheet";
|
||||
}
|
||||
}
|
||||
},
|
||||
draw() {
|
||||
if (page.tab !== "sheet") {
|
||||
return
|
||||
}
|
||||
drawIcon(x, y, icon, COLOR.BLACK);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
makeTabButton("sheet", 120, 0, sheetsIcon);
|
||||
makeTrashButton(0, 0, trashIcon);
|
||||
|
||||
const update = () => {
|
||||
buttons.forEach(button => button.update());
|
||||
if (page.tab === "code") {
|
||||
codetab.update();
|
||||
} else if (page.tab === "spritesheet") {
|
||||
spritetab.update();
|
||||
} else if (page.tab === "map") {
|
||||
maptab.update();
|
||||
} else if (page.tab === "sheet") {
|
||||
viewsheets.update();
|
||||
} else if (page.tab === "none") {
|
||||
nonetab.update();
|
||||
}
|
||||
}
|
||||
|
||||
const draw = () => {
|
||||
clearScreen();
|
||||
if (page.tab === "code") {
|
||||
codetab.draw();
|
||||
} else if (page.tab === "spritesheet") {
|
||||
spritetab.draw();
|
||||
} else if (page.tab === "map") {
|
||||
maptab.draw();
|
||||
} else if (page.tab === "sheet") {
|
||||
viewsheets.draw();
|
||||
} else if (page.tab === "none") {
|
||||
nonetab.draw();
|
||||
}
|
||||
fillRect(0, 0, 128, 8, COLOR.RED);
|
||||
fillRect(0, 120, 128, 8, COLOR.RED);
|
||||
buttons.forEach(button => button.draw());
|
||||
}
|
||||
|
||||
export const editmode = {
|
||||
update,
|
||||
draw,
|
||||
}
|
213
editor/maptab.ts
Normal file
213
editor/maptab.ts
Normal file
@ -0,0 +1,213 @@
|
||||
import { clearScreen, fillRect } from "../io/window.ts";
|
||||
import { drawSprite, drawText, useSpritesheet } from "../runtime/builtins.ts";
|
||||
import { COLOR } from "../data/colors.ts";
|
||||
import { getMapSheet, getSheet, setSheet } from "../io/sheet.ts";
|
||||
import { M, mouseClick, mouseDown, mouseHeld, mousePos } from "../io/mouse.ts";
|
||||
import { drawTransparentRect, drawVoidRect, inRect, reGrid } from "../util/util.ts";
|
||||
import { page } from "./viewsheets.ts";
|
||||
import { keyPressed, K } from "../io/keyboard.ts";
|
||||
|
||||
const state = {
|
||||
selectedSpriteSheet: 0,
|
||||
selectedSprite: 0,
|
||||
viewX: 0,
|
||||
viewY: 0,
|
||||
dragging: false,
|
||||
dragFromViewX: 0,
|
||||
dragFromViewY: 0,
|
||||
dragFromX: 0,
|
||||
dragFromY: 0,
|
||||
get spriteSheetPage() {
|
||||
return Math.floor(this.selectedSprite/64);
|
||||
},
|
||||
set spriteSheetPage(val) {
|
||||
this.selectedSprite = 64*val+this.spriteWithinPage;
|
||||
},
|
||||
get spriteWithinPage() {
|
||||
return this.selectedSprite%64;
|
||||
},
|
||||
set spriteWithinPage(val) {
|
||||
this.selectedSprite = 64*this.spriteSheetPage+val;
|
||||
},
|
||||
get map() {
|
||||
return getMapSheet(page.activeSheet);
|
||||
},
|
||||
set map(val) {
|
||||
setSheet(page.activeSheet, "map", val);
|
||||
},
|
||||
setInPatch(i: number, sprsheet: number, sprite: number) {
|
||||
const cellVal = this.map.subgrid(this.viewX, this.viewY, patchW, patchH).values[i];
|
||||
if (cellVal) {
|
||||
cellVal[0] = sprsheet
|
||||
cellVal[1] = sprite;
|
||||
}
|
||||
},
|
||||
get patch() {
|
||||
return this.map.subgrid(this.viewX, this.viewY, patchW, patchH);
|
||||
}
|
||||
}
|
||||
|
||||
const patchX = 0;
|
||||
const patchY = 8;
|
||||
const patchW = 16;
|
||||
const patchH = 9;
|
||||
|
||||
const spriteW = 8;
|
||||
const spriteH = 8;
|
||||
|
||||
const spriteSheetX = 0;
|
||||
const spriteSheetY = 88;
|
||||
const spriteSheetW = 16;
|
||||
const spriteSheetH = 4;
|
||||
|
||||
const spriteSheetPickerX = 0;
|
||||
const spriteSheetPickerY = 81;
|
||||
const spriteSheetPickerW = 16;
|
||||
const spriteSheetPickerH = 1;
|
||||
const spriteSheetPickerTabW = 7;
|
||||
const spriteSheetPickerTabH = 7;
|
||||
|
||||
const spriteSheetPageSwapX = 121;
|
||||
const spriteSheetPageSwapY = 81;
|
||||
const spriteSheetPageSwapW = 7;
|
||||
const spriteSheetPageSwapH = 7;
|
||||
|
||||
const update = () => {
|
||||
const {x: mouseX, y: mouseY} = mousePos();
|
||||
const inPatch = inRect(mouseX, mouseY, patchX, patchY, patchW*spriteW, patchH*spriteH - 2);
|
||||
const inSpriteSheetPicker = inRect(mouseX, mouseY, spriteSheetPickerX, spriteSheetPickerY, spriteSheetPickerW*spriteSheetPickerTabW, spriteSheetPickerH*spriteSheetPickerTabH);
|
||||
const inSpriteSheet = inRect(mouseX, mouseY, spriteSheetX, spriteSheetY, spriteSheetW*spriteW, spriteSheetH*spriteH);
|
||||
const inSpriteSheetPageSwap = inRect(mouseX, mouseY, spriteSheetPageSwapX, spriteSheetPageSwapY, spriteSheetPageSwapW, spriteSheetPageSwapH);
|
||||
if (mouseHeld()) {
|
||||
if (inPatch) {
|
||||
const {x, y} = reGrid(mouseX, mouseY, patchX, patchY, spriteW, spriteH);
|
||||
const cellNumber = patchW*y+x;
|
||||
state.setInPatch(cellNumber, state.selectedSpriteSheet, state.selectedSprite);
|
||||
}
|
||||
if (inSpriteSheetPicker) {
|
||||
const {x, y} = reGrid(mouseX, mouseY, spriteSheetPickerX, spriteSheetPickerY, spriteSheetPickerTabW, spriteSheetPickerTabH);
|
||||
state.selectedSpriteSheet = spriteSheetPickerW*y+x;
|
||||
}
|
||||
if (inSpriteSheet) {
|
||||
const {x, y} = reGrid(mouseX, mouseY, spriteSheetX, spriteSheetY, spriteW, spriteH);
|
||||
state.spriteWithinPage = spriteSheetW*y+x;
|
||||
}
|
||||
} else if (mouseDown(M.MIDDLE)) {
|
||||
if (inPatch) {
|
||||
const {x, y} = reGrid(mouseX, mouseY, patchX, patchY, spriteW, spriteH);
|
||||
state.dragging = true;
|
||||
state.dragFromX = x;
|
||||
state.dragFromY = y;
|
||||
state.dragFromViewX = state.viewX;
|
||||
state.dragFromViewY = state.viewY;
|
||||
}
|
||||
} else if (mouseHeld(M.MIDDLE)) {
|
||||
if (state.dragging) {
|
||||
const {x, y} = reGrid(mouseX, mouseY, patchX, patchY, spriteW, spriteH);
|
||||
state.viewX = state.dragFromViewX - x + state.dragFromX;
|
||||
state.viewY = state.dragFromViewY - y + state.dragFromY;
|
||||
}
|
||||
} else if (mouseClick(M.MIDDLE)) {
|
||||
state.dragging = false;
|
||||
} else if (mouseClick()) {
|
||||
if (inSpriteSheetPageSwap) {
|
||||
state.spriteSheetPage = (1+state.spriteSheetPage)%2;
|
||||
}
|
||||
}
|
||||
if (keyPressed(K.ARROW_RIGHT)) {
|
||||
state.viewX += 1;
|
||||
}
|
||||
if (keyPressed(K.ARROW_LEFT)) {
|
||||
state.viewX -= 1;
|
||||
}
|
||||
if (keyPressed(K.ARROW_UP)) {
|
||||
state.viewY -= 1;
|
||||
}
|
||||
if (keyPressed(K.ARROW_DOWN)) {
|
||||
state.viewY += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const outlineRect = (x: number, y: number, w: number, h: number, color: number) => {
|
||||
fillRect(x, y, w, 1, color);
|
||||
fillRect(x, y, 1, h, color);
|
||||
fillRect(x+w-1, y, 1, h, color);
|
||||
fillRect(x, y+h-1, w, 1, color);
|
||||
}
|
||||
|
||||
const draw = () => {
|
||||
const {
|
||||
selectedSpriteSheet,
|
||||
spriteWithinPage,
|
||||
spriteSheetPage,
|
||||
} = state;
|
||||
clearScreen();
|
||||
fillRect(0, 8, 128, 112, COLOR.DARKGRAY);
|
||||
|
||||
// Draw the current patch
|
||||
drawVoidRect(patchX-1, patchY-1, (patchW*spriteW)+2, (patchH*spriteH)+2);
|
||||
state.patch.values.forEach((val, i) => {
|
||||
const spriteX = patchX+spriteW*(i%patchW);
|
||||
const spriteY = patchY+spriteH*Math.floor(i/patchW);
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
const [sprsheet, sprite] = val;
|
||||
drawTransparentRect(spriteX, spriteY, 8, 8);
|
||||
if (getSheet(sprsheet).sheet_type === "spritesheet") {
|
||||
useSpritesheet(sprsheet);
|
||||
drawSprite(spriteX, spriteY, sprite);
|
||||
}
|
||||
});
|
||||
|
||||
// Draw the bar
|
||||
fillRect(spriteSheetPickerX, spriteSheetPickerY-1, 128, 1, COLOR.BLACK);
|
||||
fillRect(spriteSheetPickerX, spriteSheetPickerY, 128, 7, COLOR.DARKGRAY);
|
||||
|
||||
// Draw the spritesheet picker
|
||||
fillRect(spriteSheetPickerX, spriteSheetPickerY, spriteSheetPickerTabW*spriteSheetPickerW, spriteSheetPickerTabW, COLOR.BLACK);
|
||||
Array(spriteSheetPickerW*spriteSheetPickerH).fill(0).forEach((_, i) => {
|
||||
const tabX = spriteSheetPickerX+spriteSheetPickerTabW*(i%spriteSheetPickerW);
|
||||
const tabY = spriteSheetPickerY+spriteSheetPickerTabH*Math.floor(i/spriteSheetPickerW);
|
||||
let color = COLOR.DARKGREEN;
|
||||
if (getSheet(i).sheet_type !== "spritesheet") {
|
||||
color = COLOR.BROWN;
|
||||
}
|
||||
if (i === page.activeSheet) {
|
||||
color = COLOR.PURPLE;
|
||||
}
|
||||
if (i === selectedSpriteSheet) {
|
||||
color = {
|
||||
[COLOR.BROWN]: COLOR.TAN,
|
||||
[COLOR.DARKGREEN]: COLOR.GREEN,
|
||||
[COLOR.PURPLE]: COLOR.PINK,
|
||||
}[color];
|
||||
}
|
||||
fillRect(tabX, tabY, spriteSheetPickerTabW, spriteSheetPickerTabH, color);
|
||||
drawText(tabX+2, tabY+1, ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"][i]);
|
||||
});
|
||||
|
||||
// Draw the spritesheet page swap button
|
||||
fillRect(spriteSheetPageSwapX, spriteSheetPageSwapY, spriteSheetPageSwapW, spriteSheetPageSwapH, COLOR.BLUE);
|
||||
drawText(spriteSheetPageSwapX+2, spriteSheetPageSwapY+1, state.spriteSheetPage.toString());
|
||||
|
||||
// Draw the spritesheet
|
||||
fillRect(spriteSheetX, spriteSheetY, (spriteSheetW*spriteW), (spriteSheetH*spriteH), COLOR.BLACK);
|
||||
if (getSheet(selectedSpriteSheet).sheet_type === "spritesheet") {
|
||||
useSpritesheet(selectedSpriteSheet);
|
||||
Array(64).fill(0).forEach((_, i) => {
|
||||
const sprI = i+64*spriteSheetPage;
|
||||
const sprX = spriteSheetX+spriteW*(i%spriteSheetW);
|
||||
const sprY = spriteSheetY+spriteH*Math.floor(i/spriteSheetW);
|
||||
drawSprite(sprX, sprY, sprI);
|
||||
if (i === spriteWithinPage) {
|
||||
outlineRect(sprX, sprY, spriteW, spriteH, COLOR.WHITE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const maptab = {
|
||||
update,
|
||||
draw,
|
||||
}
|
62
editor/nonetab.ts
Normal file
62
editor/nonetab.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { clearScreen, fillRect } from "../io/window.ts";
|
||||
import { drawIcon, drawText, useSpritesheet } from "../runtime/builtins.ts";
|
||||
import { COLOR } from "../data/colors.ts";
|
||||
import { getSheet, setSheet } from "../io/sheet.ts";
|
||||
import { mouseClick, mousePos } from "../io/mouse.ts";
|
||||
import { reGridWithGap } from "../util/util.ts";
|
||||
import { page } from "./viewsheets.ts";
|
||||
import { codeIcon, mapIcon, spriteIcon } from "../data/icons.ts";
|
||||
|
||||
const gridX = 8;
|
||||
const gridY = 40;
|
||||
const cellW = 8;
|
||||
const cellH = 8;
|
||||
const gapX = 8;
|
||||
const gapY = 8;
|
||||
|
||||
const sheetTypes = ["code", "spritesheet", "map"] as const;
|
||||
const defaultSheetVal = {
|
||||
code: () => "",
|
||||
spritesheet: () => Array(128).fill(0).map(() => Array(64).fill(0)),
|
||||
map: () => Array(64*64).fill(0).map(() => [0, 0]),
|
||||
none: () =>null,
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
if (mouseClick()) {
|
||||
const {x: mouseX, y: mouseY} = mousePos();
|
||||
const g = reGridWithGap(mouseX, mouseY, gridX, gridY, cellW, cellH, gapX, gapY);
|
||||
if (g) {
|
||||
const {x, y: _y} = g;
|
||||
const sheetType = sheetTypes[x];
|
||||
setSheet(page.activeSheet, sheetType, defaultSheetVal[sheetType]());
|
||||
page.tab = getSheet(page.activeSheet).sheet_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
const draw = () => {
|
||||
clearScreen();
|
||||
useSpritesheet(page.activeSheet);
|
||||
fillRect(0, 8, 128, 112, COLOR.BLACK);
|
||||
|
||||
drawText(4, 16, "Click an icon below to choose");
|
||||
drawText(4, 16+7, "this sheet's type...");
|
||||
|
||||
// Draw the spritesheet
|
||||
sheetTypes.forEach((sheetType, i) => {
|
||||
const sx = gridX+(cellW+gapX)*(i%6);
|
||||
const sy = gridY+(cellH+gapY)*Math.floor(i/6);
|
||||
const icon = {
|
||||
code: codeIcon,
|
||||
spritesheet: spriteIcon,
|
||||
map: mapIcon,
|
||||
none: null,
|
||||
}[sheetType];
|
||||
drawIcon(sx, sy, icon, COLOR.BLUE);
|
||||
});
|
||||
}
|
||||
|
||||
export const nonetab = {
|
||||
update,
|
||||
draw,
|
||||
}
|
133
editor/spritetab.ts
Normal file
133
editor/spritetab.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import { clearScreen, fillRect } from "../io/window.ts";
|
||||
import { drawSprite, drawText, useSpritesheet } from "../runtime/builtins.ts";
|
||||
import { COLOR } from "../data/colors.ts";
|
||||
import { getSpriteSheet, setSheet } from "../io/sheet.ts";
|
||||
import { mouseClick, mouseHeld, mousePos } from "../io/mouse.ts";
|
||||
import { drawTransparentRect, inRect, outlineRect, reGrid } from "../util/util.ts";
|
||||
import { page } from "./viewsheets.ts";
|
||||
|
||||
const state = {
|
||||
selectedSprite: 0,
|
||||
selectedColor: 0,
|
||||
get spriteSheetPage() {
|
||||
return Math.floor(this.selectedSprite/64);
|
||||
},
|
||||
set spriteSheetPage(val) {
|
||||
this.selectedSprite = 64*val+this.spriteWithinPage;
|
||||
},
|
||||
get spriteWithinPage() {
|
||||
return this.selectedSprite%64;
|
||||
},
|
||||
set spriteWithinPage(val) {
|
||||
this.selectedSprite = 64*this.spriteSheetPage+val;
|
||||
},
|
||||
get sprites() {
|
||||
return getSpriteSheet(page.activeSheet);
|
||||
},
|
||||
set sprites(val) {
|
||||
setSheet(page.activeSheet, "spritesheet", val);
|
||||
}
|
||||
}
|
||||
|
||||
const paletteX = 88;
|
||||
const paletteY = 16;
|
||||
const swatchW = 8;
|
||||
const swatchH = 8;
|
||||
const paletteW = 4;
|
||||
const paletteH = 6;
|
||||
|
||||
const spriteX = 8;
|
||||
const spriteY = 16;
|
||||
const pixelW = 8;
|
||||
const pixelH = 8;
|
||||
|
||||
const spriteW = 8;
|
||||
const spriteH = 8;
|
||||
|
||||
const sheetX = 0;
|
||||
const sheetY = 88;
|
||||
const sheetW = 16;
|
||||
const sheetH = 4;
|
||||
|
||||
const spriteSheetPageSwapX = 121;
|
||||
const spriteSheetPageSwapY = 80;
|
||||
const spriteSheetPageSwapW = 7;
|
||||
const spriteSheetPageSwapH = 7;
|
||||
|
||||
const update = () => {
|
||||
if (mouseHeld()) {
|
||||
const {x: mouseX, y: mouseY} = mousePos();
|
||||
const inPalette = inRect(mouseX, mouseY, paletteX, paletteY, paletteW*swatchW, paletteH*swatchH);
|
||||
const inSprite = inRect(mouseX, mouseY, spriteX, spriteY, spriteW*pixelW, spriteH*pixelH);
|
||||
const inSheet = inRect(mouseX, mouseY, sheetX, sheetY, sheetW*spriteW, sheetH*spriteH);
|
||||
if (inPalette) {
|
||||
const {x, y} = reGrid(mouseX, mouseY, paletteX, paletteY, swatchW, swatchH);
|
||||
state.selectedColor = paletteW*y+x;
|
||||
}
|
||||
if (inSprite) {
|
||||
const {x, y} = reGrid(mouseX, mouseY, spriteX, spriteY, pixelW, pixelH);
|
||||
const pixelNumber = spriteW*y+x;
|
||||
state.sprites[state.selectedSprite][pixelNumber] = state.selectedColor;
|
||||
}
|
||||
if (inSheet) {
|
||||
const {x, y} = reGrid(mouseX, mouseY, sheetX, sheetY, spriteW, spriteH);
|
||||
state.spriteWithinPage = sheetW*y+x;
|
||||
}
|
||||
} else if (mouseClick()) {
|
||||
const {x: mouseX, y: mouseY} = mousePos();
|
||||
const inSpriteSheetPageSwap = inRect(mouseX, mouseY, spriteSheetPageSwapX, spriteSheetPageSwapY, spriteSheetPageSwapW, spriteSheetPageSwapH);
|
||||
if (inSpriteSheetPageSwap) {
|
||||
state.spriteSheetPage = (1+state.spriteSheetPage)%2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const draw = () => {
|
||||
const {sprites, selectedSprite, selectedColor} = state;
|
||||
clearScreen();
|
||||
useSpritesheet(page.activeSheet);
|
||||
fillRect(0, 8, 128, 112, COLOR.DARKGRAY);
|
||||
|
||||
// Draw the palette
|
||||
fillRect(paletteX-1, paletteY-1, (paletteW*swatchW)+2, (paletteH*swatchH)+2, COLOR.BLACK);
|
||||
Object.keys(COLOR).forEach((name, i) => {
|
||||
const swatchX = paletteX+swatchW*(i%paletteW);
|
||||
const swatchY = paletteY+swatchH*Math.floor(i/paletteW);
|
||||
fillRect(swatchX, swatchY, swatchW, swatchH, COLOR[name as keyof typeof COLOR]);
|
||||
if (name == "TRANSPARENT") {
|
||||
// transparent
|
||||
drawTransparentRect(swatchX, swatchY, swatchW, swatchH);
|
||||
}
|
||||
if (i === selectedColor) {
|
||||
outlineRect(swatchX, swatchY, swatchW, swatchH, name === "WHITE" ? COLOR.BLACK : COLOR.WHITE);
|
||||
}
|
||||
});
|
||||
|
||||
// Draw the current sprite
|
||||
fillRect(spriteX-1, spriteY-1, (spriteW*pixelW)+2, (spriteH*pixelH)+2, COLOR.BLACK);
|
||||
drawTransparentRect(spriteX, spriteY, (spriteW*pixelW), (spriteH*pixelH));
|
||||
sprites[selectedSprite].forEach((pix, i) => {
|
||||
fillRect(spriteX+pixelW*(i%spriteW), spriteY+pixelH*Math.floor(i/spriteW), pixelW, pixelH, pix);
|
||||
});
|
||||
|
||||
// Draw the spritesheet page swap button
|
||||
fillRect(spriteSheetPageSwapX, spriteSheetPageSwapY, spriteSheetPageSwapW, spriteSheetPageSwapH, COLOR.BLUE);
|
||||
drawText(spriteSheetPageSwapX+2, spriteSheetPageSwapY+1, state.spriteSheetPage.toString());
|
||||
|
||||
// Draw the spritesheet
|
||||
fillRect(sheetX, sheetY-1, (sheetW*spriteW), (sheetH*spriteH)+1, COLOR.BLACK);
|
||||
Array(64).fill(0).forEach((_, i) => {
|
||||
const sprI = i+64*state.spriteSheetPage;
|
||||
const sprX = sheetX+spriteW*(i%sheetW);
|
||||
const sprY = sheetY+spriteH*Math.floor(i/sheetW);
|
||||
drawSprite(sprX, sprY, sprI);
|
||||
if (i === state.spriteWithinPage) {
|
||||
outlineRect(sprX, sprY, spriteW, spriteH, COLOR.WHITE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const spritetab = {
|
||||
update,
|
||||
draw,
|
||||
}
|
63
editor/viewsheets.ts
Normal file
63
editor/viewsheets.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { clearScreen, fillRect } from "../io/window.ts";
|
||||
import { drawIcon, drawText } from "../runtime/builtins.ts";
|
||||
import { COLOR } from "../data/colors.ts";
|
||||
import { getSheet } from "../io/sheet.ts";
|
||||
import { mouseClick, mousePos } from "../io/mouse.ts";
|
||||
import { getCart } from "../io/cart.ts";
|
||||
import { font } from "../data/font.ts";
|
||||
import { codeIcon, spriteIcon, mapIcon } from "../data/icons.ts";
|
||||
import { reGridWithGap } from "../util/util.ts";
|
||||
|
||||
const fontHeight = font.height;
|
||||
|
||||
export const page = {activeSheet: 0, tab: "sheet"};
|
||||
|
||||
const gridX = 8;
|
||||
const gridY = 20;
|
||||
const cellW = 22;
|
||||
const cellH = 16;
|
||||
const gapX = 8;
|
||||
const gapY = 8;
|
||||
|
||||
const update = () => {
|
||||
if (mouseClick()) {
|
||||
const {x: mouseX, y: mouseY} = mousePos();
|
||||
const g = reGridWithGap(mouseX, mouseY, gridX, gridY, cellW, cellH, gapX, gapY);
|
||||
if (g) {
|
||||
const {x, y} = g;
|
||||
page.activeSheet = 4*y+x;
|
||||
const sheet = getSheet(page.activeSheet);
|
||||
if (!sheet) {
|
||||
console.log(x, y, g);
|
||||
}
|
||||
page.tab = getSheet(page.activeSheet).sheet_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const draw = () => {
|
||||
clearScreen();
|
||||
fillRect(0, 8, 128, 112, COLOR.DARKGRAY);
|
||||
|
||||
// Draw the sheet grid
|
||||
getCart().forEach((sheet, i) => {
|
||||
const x = gridX+(cellW+gapX)*(i%4);
|
||||
const y = gridY+(cellH+gapY)*Math.floor(i/4);
|
||||
fillRect(x, y, cellW, cellH, i===page.activeSheet ? COLOR.PURPLE : COLOR.BLACK);
|
||||
drawText(x+(cellW)/2, y+(cellH-fontHeight)/2, i.toString().padStart(2,"0"));
|
||||
const icon = {
|
||||
code: codeIcon,
|
||||
spritesheet: spriteIcon,
|
||||
map: mapIcon,
|
||||
none: null,
|
||||
}[sheet.sheet_type];
|
||||
if (icon) {
|
||||
drawIcon(x+2, y+4, icon, COLOR.WHITE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const viewsheets = {
|
||||
update,
|
||||
draw,
|
||||
}
|
Reference in New Issue
Block a user