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,
|
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 = {
|
||||||
|
112
codetab.ts
112
codetab.ts
@ -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();
|
||||||
}
|
}
|
||||||
|
20
keyboard.ts
20
keyboard.ts
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
repl.ts
6
repl.ts
@ -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 = {
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user