Allow variable width in font
This commit is contained in:
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user