diff --git a/builtins.ts b/builtins.ts index 3c8a9d4..c9e709c 100644 --- a/builtins.ts +++ b/builtins.ts @@ -7,6 +7,7 @@ import { font } from "./font.ts"; // import { keyDown, keyPressed, keyReleased } from "./keyboard.ts"; import { addToContext } from "./runcode.ts"; import { resetRepl } from "./repl.ts"; +import { COLOR } from "./colors.ts"; // deno-fmt-ignore const sprites = [ @@ -46,13 +47,13 @@ export const drawSprite = (x: number, y: number, spr: number) => { setPixelsInRect(x, y, 8, sprites[spr]); } -export const drawChar = (x: number, y: number, char: string) => { - setPixelsInRect(x, y, 4, font[char]); +export const drawChar = (x: number, y: number, char: string, color: number) => { + setPixelsInRect(x, y, 4, font[char].map(n => n*color)); } -export const drawText = (x: number, y: number, text: string) => { +export const drawText = (x: number, y: number, text: string, color?: number) => { [...text].forEach((char, i) => { - drawChar(x+4*i, y, char); + drawChar(x+4*i, y, char, color ?? COLOR.WHITE); }); } diff --git a/cart_unpacked.json b/cart_unpacked.json index 77e6a25..9a21964 100644 --- a/cart_unpacked.json +++ b/cart_unpacked.json @@ -1,7 +1,7 @@ [ { "sheet_type": "code", - "value": "x = code_sheet(1);\nreturn ({init: () => {y = 0}, update: () => {y += speed; if (y > 127) {y = -6}}, draw: () => {cls(); txt(x, y, 'hello world')}})" + "value": "x = code_sheet(1);\nreturn {\n\tinit: () => {y = 0},\n\tupdate: () => {\n\t\ty += speed;\n\t\tif (y > 127) {\n\t\t\ty = -6\n\t\t}\n\t},\n\tdraw: () => {\n\t\tcls();\n\t\ttxt(x, y, 'hello world')\n\t}\n}" }, { "sheet_type": "code", diff --git a/colors.ts b/colors.ts new file mode 100644 index 0000000..0eb935f --- /dev/null +++ b/colors.ts @@ -0,0 +1,13 @@ +const colors = { + BLACK: [0, 0, 0], + WHITE: [1, 1, 1], + RED: [1, 0, 0], + YELLOW: [1, 1, 0], + GREEN: [0, 1, 0], + BLUE: [0, 0, 1], + DARKBLUE: [0.1, 0.05, 0.4], +} as const; + +export const palette: Array<[number, number, number, number]> = Object.values(colors).map(val => [...val, 1]); + +export const COLOR = Object.fromEntries(Object.keys(colors).map((name, i) => [name, Number(i)])) as {[key in keyof typeof colors]: number}; \ No newline at end of file diff --git a/editmode.ts b/editmode.ts index b34a4f8..aa377bd 100644 --- a/editmode.ts +++ b/editmode.ts @@ -1,11 +1,179 @@ -import { clearScreen } from "./window.ts" +import { clearScreen, fillRect } from "./window.ts"; +import { font, fontWidth, fontHeight } from "./font.ts"; +import { drawText } from "./builtins.ts"; +import { COLOR } from "./colors.ts"; +import { getCart } from "./cart.ts"; +import {getSheet, setSheet} from "./sheet.ts"; +import { K, getKeyboardString, getKeysPressed, keyDown, keyPressed, shiftKeyDown } from "./keyboard.ts"; + +// 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); + }, + // indent(char) { + // const lines = this.code.split("\n"). + // }, + 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(""); + } + }, + 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, + } +} + +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") { + const {code, anchor, focus, focusX, focusY} = codeTabState; + const keyboardString = getKeyboardString(); + if (keyboardString) { + codeTabState.insertText(keyboardString); + } + if (keyPressed(K.ENTER)) { + codeTabState.insertText("\n"); + } + if (keyPressed(K.TAB)) { + if (!shiftKeyDown()) { + if (codeTabState.isCollapsed()) { + codeTabState.insertText("\n"); + } else { + // codeTabState.indent("\t"); + } + } else { + // codeTabState.outdent(/\t| /); + } + } + if (keyPressed(K.BACKSPACE)) { + codeTabState.backspace(); + } + 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}); + } + } + } } const draw = () => { - clearScreen() + clearScreen(); + if (tab === "code") { + drawCodeField(getSheet(0), 0, 8, 128, 112); + } } export const editmode = { diff --git a/font.ts b/font.ts index aa11fd5..ab8d012 100644 --- a/font.ts +++ b/font.ts @@ -14,6 +14,9 @@ * 96 chars * 9 bytes = */ +export const fontWidth = 4; +export const fontHeight = 6; + // deno-fmt-ignore export const font: {[key: string]: Array} = { "A": [ @@ -456,6 +459,14 @@ export const font: {[key: string]: Array} = { 0, 0, 0, 0, 0, 0, 0, 0, ], + "\t": [ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ], "<": [ 0, 0, 0, 1, 0, 0, 1, 0, diff --git a/keyboard.ts b/keyboard.ts index 9cc654b..0e16d79 100644 --- a/keyboard.ts +++ b/keyboard.ts @@ -1,3 +1,5 @@ +import { font } from "./font.ts"; + const keyboard = new Map(); export const K = { @@ -125,4 +127,22 @@ export const getKeysPressed = () => { return value.first || value.repeat; }).map(([key]) => key); return result; +} + +export const getKeyboardString = () => { + let str = ""; + for (const key of getKeysPressed()) { + let char = String.fromCharCode(key).toLowerCase(); + if (shiftKeyDown()) { + if (char in shiftMap) { + char = shiftMap[char as keyof typeof shiftMap]; + } else { + char = char.toUpperCase(); + } + } + if (char in font) { + str += char; + } + } + return str; } \ No newline at end of file diff --git a/repl.ts b/repl.ts index 54c2123..34d056a 100644 --- a/repl.ts +++ b/repl.ts @@ -3,6 +3,7 @@ import { getKeysPressed, shiftKeyDown, shiftMap, K } from "./keyboard.ts"; import { font } from "./font.ts"; import { addToContext, evalCode } from "./runcode.ts"; import { clearScreen, fillRect } from "./window.ts"; +import { COLOR } from "./colors.ts"; const lineHeight = 6; @@ -41,6 +42,7 @@ addToContext("print", print); addToContext("printVal", printVal); const update = () => { + // TODO: model this after the newer editmode.ts version using getKeyboardString for (const key of getKeysPressed()) { let char = String.fromCharCode(key).toLowerCase(); if (shiftKeyDown()) { @@ -108,7 +110,7 @@ const update = () => { const drawTextAbove = () => { textLinesAbove.forEach((line, i) => { - fillRect(0, 1+i*lineHeight, 4*(line.length+1)+1, lineHeight+1, 0); + fillRect(0, 1+i*lineHeight, 4*(line.length+1)+1, lineHeight+1, COLOR.BLACK); drawText(-1, 1+i*lineHeight, line); }); } @@ -118,8 +120,8 @@ const draw = () => { drawTextAbove(); - fillRect(0, 1+textLinesAbove.length*lineHeight, 4*(2+maxLineLen+1)+1, lineHeight+1, 0); - fillRect((2+index)*4, textLinesAbove.length*lineHeight+1, 4, lineHeight-1, 3); + fillRect(0, 1+textLinesAbove.length*lineHeight, 4*(2+maxLineLen+1)+1, lineHeight+1, COLOR.BLACK); + fillRect((2+index)*4, textLinesAbove.length*lineHeight+1, 4, lineHeight-1, COLOR.RED); drawText(-1, 1+textLinesAbove.length*lineHeight, "> "+currentLine); } diff --git a/sheet.ts b/sheet.ts index 3a45030..95df41c 100644 --- a/sheet.ts +++ b/sheet.ts @@ -3,10 +3,15 @@ import { runCode, addToContext } from "./runcode.ts"; export type SheetType = "code" | "spritesheet" | "map" | "sfx" | "patterns" | "fonts"; -const getSheet = (n: number) => { +export const getSheet = (n: number) => { return getCart()[n].value; } +// deno-lint-ignore no-explicit-any +export const setSheet = (n: number, type: SheetType, value: any) => { + return getCart()[n] = {sheet_type: type, value}; +} + export const codeSheet = (sheet: number) => { const code = getSheet(sheet); return runCode(code); diff --git a/window.ts b/window.ts index 72585a8..28da5d8 100644 --- a/window.ts +++ b/window.ts @@ -4,6 +4,7 @@ import { gl, } from "./deps.ts"; export {mainloop} from "./deps.ts"; +import { palette } from "./colors.ts"; const window = createWindow({ title: "Faux", @@ -113,16 +114,6 @@ const px = (x: number, y: number) => { ]; } -const palette: Array<[number, number, number, number]> = [ - [0, 0, 0, 0], - [1, 1, 1, 1], - [0, 0, 0, 1], - [1, 0, 0, 1], - [1, 1, 0, 1], - [0, 1, 0, 1], - [0, 0, 1, 1], -]; - const paletteX6 = palette.map(rgba => [...rgba, ...rgba, ...rgba, ...rgba, ...rgba, ...rgba]); const c = (n: number) => {