Reorganize

This commit is contained in:
dylan
2023-05-18 19:45:49 -07:00
parent 9b48560cce
commit f652e8d20f
22 changed files with 77 additions and 81 deletions

694
editor/codetab.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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,
}