Allow variable width in font
This commit is contained in:
parent
107a5370b1
commit
ad5acdeb12
39
builtins.ts
39
builtins.ts
@ -3,7 +3,7 @@ import {
|
||||
clearScreen,
|
||||
fillRect,
|
||||
} from "./window.ts";
|
||||
import { font } from "./font.ts";
|
||||
import { Font, font } from "./font.ts";
|
||||
import { keyDown, keyPressed, keyReleased } from "./keyboard.ts";
|
||||
import { addToContext, runCode } from "./runcode.ts";
|
||||
import { resetRepl } from "./repl.ts";
|
||||
@ -32,14 +32,41 @@ export const drawIcon = (x: number, y: number, icon: Array<number>, color: numbe
|
||||
setPixelsInRect(x, y, 8, icon.map(n => n*color));
|
||||
}
|
||||
|
||||
export const drawChar = (x: number, y: number, char: string, color: number) => {
|
||||
setPixelsInRect(x, y, 4, font[char].map(n => n*color));
|
||||
export const measureCharFont = (char: string, fnt: Font) => {
|
||||
return (fnt.chars[char]?.length ?? 0)/fnt.height;
|
||||
}
|
||||
|
||||
export const drawCharFont = (x: number, y: number, char: string, fnt: Font, color: number) => {
|
||||
const w = measureCharFont(char, fnt);
|
||||
if (!fnt.chars[char]) {
|
||||
return 0;
|
||||
}
|
||||
setPixelsInRect(x, y, w, fnt.chars[char].map(n => n*color));
|
||||
return w;
|
||||
}
|
||||
|
||||
export const drawTextFont = (x: number, y: number, text: string, fnt: Font, color?: number) => {
|
||||
let dx = 0;
|
||||
[...text].forEach((char) => {
|
||||
dx += 1+drawCharFont(x+dx, y, char, fnt, color ?? COLOR.WHITE);
|
||||
});
|
||||
return dx-1;
|
||||
}
|
||||
|
||||
export const measureTextFont = (text: string, fnt: Font) => {
|
||||
let w = 0;
|
||||
[...text].forEach((char) => {
|
||||
w += measureCharFont(char, fnt)+1;
|
||||
});
|
||||
return Math.max(0, w-1);
|
||||
}
|
||||
|
||||
export const drawText = (x: number, y: number, text: string, color?: number) => {
|
||||
[...text].forEach((char, i) => {
|
||||
drawChar(x+4*i, y, char, color ?? COLOR.WHITE);
|
||||
});
|
||||
return drawTextFont(x, y, text, font, color);
|
||||
}
|
||||
|
||||
export const measureText = (text: string) => {
|
||||
return measureTextFont(text, font);
|
||||
}
|
||||
|
||||
const faux = {
|
||||
|
112
codetab.ts
112
codetab.ts
@ -1,6 +1,6 @@
|
||||
import { clearScreen, fillRect } from "./window.ts";
|
||||
import { fontWidth, fontHeight } from "./font.ts";
|
||||
import { drawText } from "./builtins.ts";
|
||||
import { font } from "./font.ts";
|
||||
import { drawText, measureText } from "./builtins.ts";
|
||||
import { COLOR } from "./colors.ts";
|
||||
import { getCodeSheet, setSheet } from "./sheet.ts";
|
||||
import { K, ctrlKeyDown, getKeyboardString, keyPressed, shiftKeyDown } from "./keyboard.ts";
|
||||
@ -10,6 +10,26 @@ import { page } from "./viewsheets.ts";
|
||||
|
||||
const historyDebounceFrames = 20;
|
||||
|
||||
const fontHeight = font.height;
|
||||
|
||||
const transformForCopy = (text: string) => {
|
||||
text = text.replaceAll("➡", "➡️");
|
||||
text = text.replaceAll("⬅", "⬅️");
|
||||
text = text.replaceAll("⬇", "⬇️");
|
||||
text = text.replaceAll("⬆", "⬆️");
|
||||
return text;
|
||||
}
|
||||
|
||||
const transformForPaste = (text: string) => {
|
||||
let newstr = "";
|
||||
for (const char of text) {
|
||||
if (char in font.chars) {
|
||||
newstr += char;
|
||||
}
|
||||
}
|
||||
return newstr;
|
||||
}
|
||||
|
||||
const state = {
|
||||
history: [{code: getCodeSheet(page.activeSheet), anchor: 0, focus: 0}],
|
||||
historyDebounce: 0,
|
||||
@ -60,6 +80,10 @@ const state = {
|
||||
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;
|
||||
},
|
||||
@ -171,30 +195,30 @@ const state = {
|
||||
async copy() {
|
||||
const {code, anchor, focus} = this;
|
||||
const selected = code.slice(Math.min(anchor,focus), Math.max(anchor,focus));
|
||||
await clipboard.writeText(selected);
|
||||
await clipboard.writeText(transformForCopy(selected));
|
||||
},
|
||||
async cut() {
|
||||
await this.copy();
|
||||
this.insertText("");
|
||||
},
|
||||
async paste() {
|
||||
this.insertText(await clipboard.readText());
|
||||
this.insertText(transformForPaste(await clipboard.readText()));
|
||||
},
|
||||
scrollToCursor() {
|
||||
const {focusY, focusX, scrollY, scrollX} = this;
|
||||
const {focusY, scrollY, scrollX, focus} = this;
|
||||
const fh = fontHeight + 1;
|
||||
const fw = fontWidth;
|
||||
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 (focusX*fw < scrollX) {
|
||||
this.scrollX = focusX*fw;
|
||||
if (rect.x < scrollX) {
|
||||
this.scrollX = rect.x;
|
||||
}
|
||||
if (focusX*fw > scrollX+128-fw) {
|
||||
this.scrollX = focusX*fw-128+fw;
|
||||
if (rect.x+rect.w > scrollX+128) {
|
||||
this.scrollX = rect.x-128+rect.w+1;
|
||||
}
|
||||
},
|
||||
currentIndentation() {
|
||||
@ -233,6 +257,38 @@ const gridToIndex = (str: string, x: number, y: number) => {
|
||||
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))+1 < x && j < line.length) {
|
||||
j+=1;
|
||||
}
|
||||
return prefix + j;
|
||||
}
|
||||
|
||||
const keywords = [
|
||||
"break",
|
||||
"case",
|
||||
@ -379,24 +435,29 @@ const drawCodeField = (code: string, x: number, y: number, w: number, h: number)
|
||||
scrollY,
|
||||
anchor,
|
||||
focus,
|
||||
focusX,
|
||||
focusY,
|
||||
} = state;
|
||||
fillRect(x, y, w, h, COLOR.DARKERBLUE);
|
||||
if (anchor === focus) {
|
||||
fillRect(x+focusX*fontWidth-scrollX, y+focusY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.YELLOW);
|
||||
const rect = indexToRect(code, focus);
|
||||
fillRect(x+rect.x-scrollX, y+rect.y-scrollY, 1, rect.h+1, COLOR.YELLOW);
|
||||
} else {
|
||||
for (let i = Math.min(anchor, focus); i < Math.max(anchor, focus); i++) {
|
||||
const {x: selX, y: selY} = indexToGrid(code, i);
|
||||
fillRect(x+selX*fontWidth-scrollX, y+selY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.WHITE);
|
||||
const sel = indexToRect(code, i);
|
||||
fillRect(x+sel.x-scrollX, y+sel.y-scrollY, sel.w+2, sel.h+1, COLOR.WHITE);
|
||||
}
|
||||
// fillRect(x+focusX*fontWidth-scrollX, y+focusY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.YELLOW);
|
||||
const rect = indexToRect(code, focus);
|
||||
fillRect(x+rect.x-scrollX, y+rect.y-scrollY, 1, rect.h+1, COLOR.YELLOW);
|
||||
}
|
||||
const builtins = Object.keys(getContext());
|
||||
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];
|
||||
@ -415,9 +476,9 @@ const drawCodeField = (code: string, x: number, y: number, w: number, h: number)
|
||||
if (builtins.includes(token.value)) {
|
||||
color = builtinColor;
|
||||
}
|
||||
drawText(x+cx-scrollX, 1+y+cy-scrollY, line, color);
|
||||
drawText(1+x+cx-scrollX, 1+y+cy-scrollY, line, color);
|
||||
if (i === lines.length-1) {
|
||||
cx += fontWidth*line.length;
|
||||
cx += measureText(line)+1;
|
||||
} else {
|
||||
cx=0;
|
||||
cy+=fontHeight+1;
|
||||
@ -427,7 +488,7 @@ const drawCodeField = (code: string, x: number, y: number, w: number, h: number)
|
||||
}
|
||||
|
||||
const update = async () => {
|
||||
const { focus, focusX, focusY} = state;
|
||||
const { focus } = state;
|
||||
if (state.historyDebounce > 0) {
|
||||
state.historyDebounce -= 1;
|
||||
if (state.historyDebounce <= 0) {
|
||||
@ -482,18 +543,23 @@ const update = async () => {
|
||||
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({x: focusX, y: focusY+1});
|
||||
state.setFocus(newIndex);
|
||||
} else {
|
||||
state.setSelection({x: focusX, y: focusY+1});
|
||||
state.setSelection(newIndex);
|
||||
}
|
||||
state.scrollToCursor();
|
||||
}
|
||||
if (keyPressed(K.ARROW_UP)) {
|
||||
const rect = indexToRect(state.code, focus);
|
||||
console.log(rect, focus, pixelToIndex(state.code, rect.x, rect.y-1));
|
||||
const newIndex = pixelToIndex(state.code, rect.x, rect.y-1-1);
|
||||
if (shiftKeyDown()) {
|
||||
state.setFocus({x: focusX, y: focusY-1});
|
||||
state.setFocus(newIndex);
|
||||
} else {
|
||||
state.setSelection({x: focusX, y: focusY-1});
|
||||
state.setSelection(newIndex);
|
||||
}
|
||||
state.scrollToCursor();
|
||||
}
|
||||
|
20
keyboard.ts
20
keyboard.ts
@ -62,6 +62,13 @@ export const shiftMap = {
|
||||
"/": "?",
|
||||
}
|
||||
|
||||
export const altMap = {
|
||||
"w": "⬆",
|
||||
"a": "⬅",
|
||||
"s": "⬇",
|
||||
"d": "➡",
|
||||
}
|
||||
|
||||
addEventListener("keydown", (evt) => {
|
||||
// console.log("keydown", evt.key, evt.key.charCodeAt(0));
|
||||
const key = evt.key.charCodeAt(0);
|
||||
@ -119,6 +126,10 @@ export const ctrlKeyDown = () => {
|
||||
return keyDown(K.CTRL_LEFT) || keyDown(K.CTRL_RIGHT);
|
||||
}
|
||||
|
||||
export const altKeyDown = () => {
|
||||
return keyDown(K.ALT_LEFT) || keyDown(K.ALT_RIGHT);
|
||||
}
|
||||
|
||||
export const keyReleased = (key: string | number) => {
|
||||
if (typeof key === "string") {
|
||||
key = key.toUpperCase().charCodeAt(0);
|
||||
@ -147,7 +158,14 @@ export const getKeyboardString = () => {
|
||||
char = char.toUpperCase();
|
||||
}
|
||||
}
|
||||
if (char in font) {
|
||||
if (altKeyDown()) {
|
||||
if (char in altMap) {
|
||||
char = altMap[char as keyof typeof altMap];
|
||||
} else {
|
||||
char = char.toUpperCase();
|
||||
}
|
||||
}
|
||||
if (char in font.chars) {
|
||||
str += char;
|
||||
}
|
||||
}
|
||||
|
6
repl.ts
6
repl.ts
@ -52,7 +52,7 @@ const update = () => {
|
||||
char = char.toUpperCase();
|
||||
}
|
||||
}
|
||||
if (char in font) {
|
||||
if (char in font.chars) {
|
||||
currentLine = currentLine.slice(0, index)+char+currentLine.slice(index);
|
||||
index += 1;
|
||||
} else if (key === K.BACKSPACE) {
|
||||
@ -111,7 +111,7 @@ const update = () => {
|
||||
const drawTextAbove = () => {
|
||||
textLinesAbove.forEach((line, i) => {
|
||||
fillRect(0, 1+i*lineHeight, 4*(line.length+1)+1, lineHeight+1, COLOR.BLACK);
|
||||
drawText(-1, 1+i*lineHeight, line);
|
||||
drawText(0, 1+i*lineHeight, line);
|
||||
});
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ const draw = () => {
|
||||
|
||||
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);
|
||||
drawText(0, 1+textLinesAbove.length*lineHeight, "> "+currentLine);
|
||||
}
|
||||
|
||||
export const repl = {
|
||||
|
@ -4,10 +4,12 @@ import { COLOR } from "./colors.ts";
|
||||
import { getSheet } from "./sheet.ts";
|
||||
import { mouseClick, mousePos } from "./mouse.ts";
|
||||
import { getCart } from "./cart.ts";
|
||||
import { fontHeight } from "./font.ts";
|
||||
import { font } from "./font.ts";
|
||||
import { codeIcon, spriteIcon } from "./icons.ts";
|
||||
import { reGridWithGap } from "./util.ts";
|
||||
|
||||
const fontHeight = font.height;
|
||||
|
||||
export const page = {activeSheet: 0, tab: "sheet"};
|
||||
|
||||
const gridX = 8;
|
||||
|
Loading…
x
Reference in New Issue
Block a user