Allow variable width in font

This commit is contained in:
dylan 2023-05-08 21:39:08 -07:00
parent 107a5370b1
commit ad5acdeb12
6 changed files with 967 additions and 806 deletions

View File

@ -3,7 +3,7 @@ import {
clearScreen, clearScreen,
fillRect, fillRect,
} from "./window.ts"; } from "./window.ts";
import { font } from "./font.ts"; import { Font, font } from "./font.ts";
import { keyDown, keyPressed, keyReleased } from "./keyboard.ts"; import { keyDown, keyPressed, keyReleased } from "./keyboard.ts";
import { addToContext, runCode } from "./runcode.ts"; import { addToContext, runCode } from "./runcode.ts";
import { resetRepl } from "./repl.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)); setPixelsInRect(x, y, 8, icon.map(n => n*color));
} }
export const drawChar = (x: number, y: number, char: string, color: number) => { export const measureCharFont = (char: string, fnt: Font) => {
setPixelsInRect(x, y, 4, font[char].map(n => n*color)); 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) => { export const drawText = (x: number, y: number, text: string, color?: number) => {
[...text].forEach((char, i) => { return drawTextFont(x, y, text, font, color);
drawChar(x+4*i, y, char, color ?? COLOR.WHITE); }
});
export const measureText = (text: string) => {
return measureTextFont(text, font);
} }
const faux = { const faux = {

View File

@ -1,6 +1,6 @@
import { clearScreen, fillRect } from "./window.ts"; import { clearScreen, fillRect } from "./window.ts";
import { fontWidth, fontHeight } from "./font.ts"; import { font } from "./font.ts";
import { drawText } from "./builtins.ts"; import { drawText, measureText } from "./builtins.ts";
import { COLOR } from "./colors.ts"; import { COLOR } from "./colors.ts";
import { getCodeSheet, setSheet } from "./sheet.ts"; import { getCodeSheet, setSheet } from "./sheet.ts";
import { K, ctrlKeyDown, getKeyboardString, keyPressed, shiftKeyDown } from "./keyboard.ts"; import { K, ctrlKeyDown, getKeyboardString, keyPressed, shiftKeyDown } from "./keyboard.ts";
@ -10,6 +10,26 @@ import { page } from "./viewsheets.ts";
const historyDebounceFrames = 20; 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 = { const state = {
history: [{code: getCodeSheet(page.activeSheet), anchor: 0, focus: 0}], history: [{code: getCodeSheet(page.activeSheet), anchor: 0, focus: 0}],
historyDebounce: 0, historyDebounce: 0,
@ -60,6 +80,10 @@ const state = {
get focusY() {return indexToGrid(this.code, this.focus).y;}, get focusY() {return indexToGrid(this.code, this.focus).y;},
get anchorX() {return indexToGrid(this.code, this.anchor).x;}, get anchorX() {return indexToGrid(this.code, this.anchor).x;},
get anchorY() {return indexToGrid(this.code, this.anchor).y;}, 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() { isCollapsed() {
return this.anchor === this.focus; return this.anchor === this.focus;
}, },
@ -171,30 +195,30 @@ const state = {
async copy() { async copy() {
const {code, anchor, focus} = this; const {code, anchor, focus} = this;
const selected = code.slice(Math.min(anchor,focus), Math.max(anchor,focus)); const selected = code.slice(Math.min(anchor,focus), Math.max(anchor,focus));
await clipboard.writeText(selected); await clipboard.writeText(transformForCopy(selected));
}, },
async cut() { async cut() {
await this.copy(); await this.copy();
this.insertText(""); this.insertText("");
}, },
async paste() { async paste() {
this.insertText(await clipboard.readText()); this.insertText(transformForPaste(await clipboard.readText()));
}, },
scrollToCursor() { scrollToCursor() {
const {focusY, focusX, scrollY, scrollX} = this; const {focusY, scrollY, scrollX, focus} = this;
const fh = fontHeight + 1; const fh = fontHeight + 1;
const fw = fontWidth; const rect = indexToRect(this.code, focus);
if (focusY*fh < scrollY) { if (focusY*fh < scrollY) {
this.scrollY = focusY*fh; this.scrollY = focusY*fh;
} }
if (focusY*fh > scrollY+112-fh) { if (focusY*fh > scrollY+112-fh) {
this.scrollY = focusY*fh-112+fh; this.scrollY = focusY*fh-112+fh;
} }
if (focusX*fw < scrollX) { if (rect.x < scrollX) {
this.scrollX = focusX*fw; this.scrollX = rect.x;
} }
if (focusX*fw > scrollX+128-fw) { if (rect.x+rect.w > scrollX+128) {
this.scrollX = focusX*fw-128+fw; this.scrollX = rect.x-128+rect.w+1;
} }
}, },
currentIndentation() { 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); 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 = [ const keywords = [
"break", "break",
"case", "case",
@ -379,24 +435,29 @@ const drawCodeField = (code: string, x: number, y: number, w: number, h: number)
scrollY, scrollY,
anchor, anchor,
focus, focus,
focusX,
focusY,
} = state; } = state;
fillRect(x, y, w, h, COLOR.DARKERBLUE); fillRect(x, y, w, h, COLOR.DARKERBLUE);
if (anchor === focus) { 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 { } else {
for (let i = Math.min(anchor, focus); i < Math.max(anchor, focus); i++) { for (let i = Math.min(anchor, focus); i < Math.max(anchor, focus); i++) {
const {x: selX, y: selY} = indexToGrid(code, i); const sel = indexToRect(code, i);
fillRect(x+selX*fontWidth-scrollX, y+selY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.WHITE); 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 builtins = Object.keys(getContext());
const tokens = [...tokenize(code)]; const tokens = [...tokenize(code)];
let cx = 0; let cx = 0;
let cy = 0; let cy = 0;
tokens.forEach((token) => { tokens.forEach((token) => {
if (token.type === "LineTerminatorSequence") {
cx=0;
cy+=fontHeight+1;
return;
}
const lines = token.value.split("\n"); const lines = token.value.split("\n");
lines.forEach((line, i) => { lines.forEach((line, i) => {
let color = tokenColors[token.type]; 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)) { if (builtins.includes(token.value)) {
color = builtinColor; 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) { if (i === lines.length-1) {
cx += fontWidth*line.length; cx += measureText(line)+1;
} else { } else {
cx=0; cx=0;
cy+=fontHeight+1; cy+=fontHeight+1;
@ -427,7 +488,7 @@ const drawCodeField = (code: string, x: number, y: number, w: number, h: number)
} }
const update = async () => { const update = async () => {
const { focus, focusX, focusY} = state; const { focus } = state;
if (state.historyDebounce > 0) { if (state.historyDebounce > 0) {
state.historyDebounce -= 1; state.historyDebounce -= 1;
if (state.historyDebounce <= 0) { if (state.historyDebounce <= 0) {
@ -482,18 +543,23 @@ const update = async () => {
state.scrollToCursor(); state.scrollToCursor();
} }
if (keyPressed(K.ARROW_DOWN)) { 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()) { if (shiftKeyDown()) {
state.setFocus({x: focusX, y: focusY+1}); state.setFocus(newIndex);
} else { } else {
state.setSelection({x: focusX, y: focusY+1}); state.setSelection(newIndex);
} }
state.scrollToCursor(); state.scrollToCursor();
} }
if (keyPressed(K.ARROW_UP)) { 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()) { if (shiftKeyDown()) {
state.setFocus({x: focusX, y: focusY-1}); state.setFocus(newIndex);
} else { } else {
state.setSelection({x: focusX, y: focusY-1}); state.setSelection(newIndex);
} }
state.scrollToCursor(); state.scrollToCursor();
} }

1208
font.ts

File diff suppressed because it is too large Load Diff

View File

@ -62,6 +62,13 @@ export const shiftMap = {
"/": "?", "/": "?",
} }
export const altMap = {
"w": "⬆",
"a": "⬅",
"s": "⬇",
"d": "➡",
}
addEventListener("keydown", (evt) => { addEventListener("keydown", (evt) => {
// console.log("keydown", evt.key, evt.key.charCodeAt(0)); // console.log("keydown", evt.key, evt.key.charCodeAt(0));
const 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); 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) => { export const keyReleased = (key: string | number) => {
if (typeof key === "string") { if (typeof key === "string") {
key = key.toUpperCase().charCodeAt(0); key = key.toUpperCase().charCodeAt(0);
@ -147,7 +158,14 @@ export const getKeyboardString = () => {
char = char.toUpperCase(); 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; str += char;
} }
} }

View File

@ -52,7 +52,7 @@ const update = () => {
char = char.toUpperCase(); char = char.toUpperCase();
} }
} }
if (char in font) { if (char in font.chars) {
currentLine = currentLine.slice(0, index)+char+currentLine.slice(index); currentLine = currentLine.slice(0, index)+char+currentLine.slice(index);
index += 1; index += 1;
} else if (key === K.BACKSPACE) { } else if (key === K.BACKSPACE) {
@ -111,7 +111,7 @@ const update = () => {
const drawTextAbove = () => { const drawTextAbove = () => {
textLinesAbove.forEach((line, i) => { textLinesAbove.forEach((line, i) => {
fillRect(0, 1+i*lineHeight, 4*(line.length+1)+1, lineHeight+1, COLOR.BLACK); 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(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); 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 = { export const repl = {

View File

@ -4,10 +4,12 @@ import { COLOR } from "./colors.ts";
import { getSheet } from "./sheet.ts"; import { getSheet } from "./sheet.ts";
import { mouseClick, mousePos } from "./mouse.ts"; import { mouseClick, mousePos } from "./mouse.ts";
import { getCart } from "./cart.ts"; import { getCart } from "./cart.ts";
import { fontHeight } from "./font.ts"; import { font } from "./font.ts";
import { codeIcon, spriteIcon } from "./icons.ts"; import { codeIcon, spriteIcon } from "./icons.ts";
import { reGridWithGap } from "./util.ts"; import { reGridWithGap } from "./util.ts";
const fontHeight = font.height;
export const page = {activeSheet: 0, tab: "sheet"}; export const page = {activeSheet: 0, tab: "sheet"};
const gridX = 8; const gridX = 8;