shellquest
Version:
A terminal-based dungeon crawler game with ASCII graphics
1,597 lines (1,578 loc) • 405 kB
JavaScript
// @bun
var __require = import.meta.require;
// src/core/ansi.ts
var ANSI = {
switchToAlternateScreen: "\x1B[?1049h",
switchToMainScreen: "\x1B[?1049l",
reset: "\x1B[0m",
hideCursor: "\x1B[?25l",
showCursor: "\x1B[?25h",
resetCursorColor: "\x1B]12;default\x07",
saveCursorState: "\x1B[s",
restoreCursorState: "\x1B[u",
enableMouseTracking: "\x1B[?1000h",
disableMouseTracking: "\x1B[?1000l",
enableButtonEventTracking: "\x1B[?1002h",
disableButtonEventTracking: "\x1B[?1002l",
enableAnyEventTracking: "\x1B[?1003h",
disableAnyEventTracking: "\x1B[?1003l",
enableSGRMouseMode: "\x1B[?1006h",
disableSGRMouseMode: "\x1B[?1006l"
};
// src/core/Renderable.ts
import { EventEmitter } from "events";
var renderableNumber = 1;
class Renderable extends EventEmitter {
static renderablesByNumber = new Map;
id;
num;
ctx = null;
_x;
_y;
_width;
_height;
_zIndex;
visible;
selectable = false;
renderableMap = new Map;
renderableArray = [];
needsZIndexSort = false;
parent = null;
constructor(id, options) {
super();
this.id = id;
this.num = renderableNumber++;
this._x = options.x;
this._y = options.y;
this._width = options.width;
this._height = options.height;
this._zIndex = options.zIndex;
this.visible = options.visible !== false;
Renderable.renderablesByNumber.set(this.num, this);
}
hasSelection() {
return false;
}
onSelectionChanged(selection) {
return false;
}
getSelectedText() {
return "";
}
shouldStartSelection(x, y) {
return false;
}
set needsUpdate(value) {
if (this.parent) {
this.parent.needsUpdate = value;
}
}
get x() {
if (this.parent) {
return this.parent.x + this._x;
}
return this._x;
}
set x(value) {
this._x = value;
}
get y() {
if (this.parent) {
return this.parent.y + this._y;
}
return this._y;
}
set y(value) {
this._y = value;
}
get width() {
return this._width;
}
set width(value) {
this._width = value;
}
get height() {
return this._height;
}
set height(value) {
this._height = value;
}
get zIndex() {
return this._zIndex;
}
set zIndex(value) {
if (this._zIndex !== value) {
this._zIndex = value;
this.parent?.requestZIndexSort();
}
}
requestZIndexSort() {
this.needsZIndexSort = true;
}
ensureZIndexSorted() {
if (this.needsZIndexSort) {
this.renderableArray.sort((a, b) => a.zIndex > b.zIndex ? 1 : a.zIndex < b.zIndex ? -1 : 0);
this.needsZIndexSort = false;
}
}
clear() {
for (const child of this.renderableArray) {
this.remove(child.id);
}
}
add(obj) {
if (this.renderableMap.has(obj.id)) {
this.remove(obj.id);
}
if (obj.parent) {
obj.parent.remove(obj.id);
}
obj.parent = this;
if (this.ctx) {
obj.ctx = this.ctx;
}
this.renderableArray.push(obj);
this.needsZIndexSort = true;
this.renderableMap.set(obj.id, obj);
this.emit("child:added", obj);
}
propagateContext(ctx) {
this.ctx = ctx;
for (const child of this.renderableArray) {
child.propagateContext(ctx);
}
}
unload() {}
getRenderable(id) {
return this.renderableMap.get(id);
}
remove(id) {
if (!id) {
return;
}
if (this.renderableMap.has(id)) {
const obj = this.renderableMap.get(id);
if (obj) {
obj.parent = null;
obj.propagateContext(null);
obj.unload();
}
this.renderableMap.delete(id);
const index = this.renderableArray.findIndex((obj2) => obj2.id === id);
if (index !== -1) {
this.renderableArray.splice(index, 1);
}
this.emit("child:removed", id);
}
}
getAllElementIds() {
return Array.from(this.renderableMap.keys());
}
getChildren() {
return [...this.renderableArray];
}
render(buffer, deltaTime) {
if (!this.visible)
return;
this.renderSelf(buffer, deltaTime);
this.ctx?.addToHitGrid(this.x, this.y, this.width, this.height, this.num);
this.ensureZIndexSorted();
for (const child of this.renderableArray) {
child.render(buffer, deltaTime);
}
}
renderSelf(buffer, deltaTime) {}
destroy() {
if (this.parent) {
throw new Error(`Cannot destroy ${this.id} while it still has a parent (${this.parent.id}). Remove from parent first.`);
}
for (const child of this.renderableArray) {
child.parent = null;
child.destroy();
}
this.renderableArray = [];
this.renderableMap.clear();
Renderable.renderablesByNumber.delete(this.num);
this.destroySelf();
}
destroySelf() {}
processMouseEvent(event) {
this.onMouseEvent(event);
if (this.parent && !event.defaultPrevented) {
this.parent.processMouseEvent(event);
}
}
onMouseEvent(event) {}
}
// src/core/types.ts
class RGBA {
buffer;
constructor(buffer) {
this.buffer = buffer;
}
static fromArray(array) {
return new RGBA(array);
}
static fromValues(r, g, b, a = 1) {
return new RGBA(new Float32Array([r, g, b, a]));
}
static fromInts(r, g, b, a = 255) {
return new RGBA(new Float32Array([r / 255, g / 255, b / 255, a / 255]));
}
static fromHex(hex) {
if (typeof hex === "number") {
return hexToRgb(`#${hex.toString(16).padStart(6, "0")}`);
} else if (!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
return hexToRgb(hex);
}
}
get r() {
return this.buffer[0];
}
set r(value) {
this.buffer[0] = value;
}
get g() {
return this.buffer[1];
}
set g(value) {
this.buffer[1] = value;
}
get b() {
return this.buffer[2];
}
set b(value) {
this.buffer[2] = value;
}
get a() {
return this.buffer[3];
}
set a(value) {
this.buffer[3] = value;
}
map(fn) {
return [fn(this.r), fn(this.g), fn(this.b), fn(this.a)];
}
toString() {
return `rgba(${this.r.toFixed(2)}, ${this.g.toFixed(2)}, ${this.b.toFixed(2)}, ${this.a.toFixed(2)})`;
}
}
var TextAttributes = {
NONE: 0,
BOLD: 1 << 0,
DIM: 1 << 1,
ITALIC: 1 << 2,
UNDERLINE: 1 << 3,
BLINK: 1 << 4,
INVERSE: 1 << 5,
HIDDEN: 1 << 6,
STRIKETHROUGH: 1 << 7
};
var DebugOverlayCorner;
((DebugOverlayCorner2) => {
DebugOverlayCorner2[DebugOverlayCorner2["topLeft"] = 0] = "topLeft";
DebugOverlayCorner2[DebugOverlayCorner2["topRight"] = 1] = "topRight";
DebugOverlayCorner2[DebugOverlayCorner2["bottomLeft"] = 2] = "bottomLeft";
DebugOverlayCorner2[DebugOverlayCorner2["bottomRight"] = 3] = "bottomRight";
})(DebugOverlayCorner ||= {});
// src/core/utils.ts
function hexToRgb(hex) {
hex = hex.replace(/^#/, "");
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
console.warn(`Invalid hex color: ${hex}, defaulting to magenta`);
return RGBA.fromValues(1, 0, 1, 1);
}
const r = parseInt(hex.substring(0, 2), 16) / 255;
const g = parseInt(hex.substring(2, 4), 16) / 255;
const b = parseInt(hex.substring(4, 6), 16) / 255;
return RGBA.fromValues(r, g, b, 1);
}
function rgbToHex(rgb) {
return "#" + [rgb.r, rgb.g, rgb.b].map((x) => {
const hex = Math.floor(Math.max(0, Math.min(1, x) * 255)).toString(16);
return hex.length === 1 ? "0" + hex : hex;
}).join("");
}
function hsvToRgb(h, s, v) {
let r = 0, g = 0, b = 0;
const i = Math.floor(h / 60) % 6;
const f = h / 60 - Math.floor(h / 60);
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
return RGBA.fromValues(r, g, b, 1);
}
var CSS_COLOR_NAMES = {
black: "#000000",
white: "#FFFFFF",
red: "#FF0000",
green: "#008000",
blue: "#0000FF",
yellow: "#FFFF00",
cyan: "#00FFFF",
magenta: "#FF00FF",
silver: "#C0C0C0",
gray: "#808080",
grey: "#808080",
maroon: "#800000",
olive: "#808000",
lime: "#00FF00",
aqua: "#00FFFF",
teal: "#008080",
navy: "#000080",
fuchsia: "#FF00FF",
purple: "#800080",
orange: "#FFA500",
brightblack: "#666666",
brightred: "#FF6666",
brightgreen: "#66FF66",
brightblue: "#6666FF",
brightyellow: "#FFFF66",
brightcyan: "#66FFFF",
brightmagenta: "#FF66FF",
brightwhite: "#FFFFFF"
};
function parseColor(color) {
if (typeof color === "string") {
const lowerColor = color.toLowerCase();
if (lowerColor === "transparent") {
return RGBA.fromValues(0, 0, 0, 0);
}
if (CSS_COLOR_NAMES[lowerColor]) {
return hexToRgb(CSS_COLOR_NAMES[lowerColor]);
}
return hexToRgb(color);
}
return color;
}
function createTextAttributes({
bold = false,
italic = false,
underline = false,
dim = false,
blink = false,
inverse = false,
hidden = false,
strikethrough = false
} = {}) {
let attributes = TextAttributes.NONE;
if (bold)
attributes |= TextAttributes.BOLD;
if (italic)
attributes |= TextAttributes.ITALIC;
if (underline)
attributes |= TextAttributes.UNDERLINE;
if (dim)
attributes |= TextAttributes.DIM;
if (blink)
attributes |= TextAttributes.BLINK;
if (inverse)
attributes |= TextAttributes.INVERSE;
if (hidden)
attributes |= TextAttributes.HIDDEN;
if (strikethrough)
attributes |= TextAttributes.STRIKETHROUGH;
return attributes;
}
async function loadTemplate(filePath, params) {
const template = await Bun.file(filePath).text();
return template.replace(/\${(\w+)}/g, (match, key) => params[key] || match);
}
function fixPaths(paths) {
if (process.env.BUN_PACKER_BUNDLE) {
return Object.fromEntries(Object.entries(paths).map(([key, value]) => [key, value.replace("../", "")]));
}
return paths;
}
// src/core/ui/lib/border.ts
var BorderChars = {
single: {
topLeft: "\u250C",
topRight: "\u2510",
bottomLeft: "\u2514",
bottomRight: "\u2518",
horizontal: "\u2500",
vertical: "\u2502",
topT: "\u252C",
bottomT: "\u2534",
leftT: "\u251C",
rightT: "\u2524",
cross: "\u253C"
},
double: {
topLeft: "\u2554",
topRight: "\u2557",
bottomLeft: "\u255A",
bottomRight: "\u255D",
horizontal: "\u2550",
vertical: "\u2551",
topT: "\u2566",
bottomT: "\u2569",
leftT: "\u2560",
rightT: "\u2563",
cross: "\u256C"
},
rounded: {
topLeft: "\u256D",
topRight: "\u256E",
bottomLeft: "\u2570",
bottomRight: "\u256F",
horizontal: "\u2500",
vertical: "\u2502",
topT: "\u252C",
bottomT: "\u2534",
leftT: "\u251C",
rightT: "\u2524",
cross: "\u253C"
},
heavy: {
topLeft: "\u250F",
topRight: "\u2513",
bottomLeft: "\u2517",
bottomRight: "\u251B",
horizontal: "\u2501",
vertical: "\u2503",
topT: "\u2533",
bottomT: "\u253B",
leftT: "\u2523",
rightT: "\u252B",
cross: "\u254B"
}
};
function getBorderFromSides(sides) {
const result = [];
if (sides.top)
result.push("top");
if (sides.right)
result.push("right");
if (sides.bottom)
result.push("bottom");
if (sides.left)
result.push("left");
return result.length > 0 ? result : false;
}
function getBorderSides(border) {
return border === true ? { top: true, right: true, bottom: true, left: true } : Array.isArray(border) ? {
top: border.includes("top"),
right: border.includes("right"),
bottom: border.includes("bottom"),
left: border.includes("left")
} : { top: false, right: false, bottom: false, left: false };
}
function drawBorder(buffer, options) {
const borderColor = parseColor(options.borderColor);
const backgroundColor = parseColor(options.backgroundColor);
const borderSides = getBorderSides(options.border);
const borders = options.customBorderChars || BorderChars[options.borderStyle];
const startX = Math.max(0, options.x);
const startY = Math.max(0, options.y);
const endX = Math.min(buffer.getWidth() - 1, options.x + options.width - 1);
const endY = Math.min(buffer.getHeight() - 1, options.y + options.height - 1);
const drawTop = borderSides.top;
let shouldDrawTitle = false;
let titleX = startX;
let titleStartX = 0;
let titleEndX = 0;
if (options.title && options.title.length > 0 && drawTop) {
const titleLength = options.title.length;
const minTitleSpace = 4;
shouldDrawTitle = options.width >= titleLength + minTitleSpace;
if (shouldDrawTitle) {
const padding = 2;
if (options.titleAlignment === "center") {
titleX = startX + Math.max(padding, Math.floor((options.width - titleLength) / 2));
} else if (options.titleAlignment === "right") {
titleX = startX + options.width - padding - titleLength;
} else {
titleX = startX + padding;
}
titleX = Math.max(startX + padding, Math.min(titleX, endX - titleLength));
titleStartX = titleX;
titleEndX = titleX + titleLength - 1;
}
}
const drawBottom = borderSides.bottom;
const drawLeft = borderSides.left;
const drawRight = borderSides.right;
const leftBorderOnly = drawLeft && !drawTop && !drawBottom;
const rightBorderOnly = drawRight && !drawTop && !drawBottom;
const bottomOnlyWithVerticals = drawBottom && !drawTop && (drawLeft || drawRight);
const topOnlyWithVerticals = drawTop && !drawBottom && (drawLeft || drawRight);
const extendVerticalsToTop = leftBorderOnly || rightBorderOnly || bottomOnlyWithVerticals;
const extendVerticalsToBottom = leftBorderOnly || rightBorderOnly || topOnlyWithVerticals;
if (drawTop || drawBottom) {
if (drawTop) {
for (let x = startX;x <= endX; x++) {
if (startY >= 0 && startY < buffer.getHeight()) {
let char = borders.horizontal;
if (x === startX) {
char = drawLeft ? borders.topLeft : borders.horizontal;
} else if (x === endX) {
char = drawRight ? borders.topRight : borders.horizontal;
}
if (shouldDrawTitle && x >= titleStartX && x <= titleEndX) {
continue;
}
buffer.setCellWithAlphaBlending(x, startY, char, borderColor, backgroundColor);
}
}
}
if (drawBottom) {
for (let x = startX;x <= endX; x++) {
if (endY >= 0 && endY < buffer.getHeight()) {
let char = borders.horizontal;
if (x === startX) {
char = drawLeft ? borders.bottomLeft : borders.horizontal;
} else if (x === endX) {
char = drawRight ? borders.bottomRight : borders.horizontal;
}
buffer.setCellWithAlphaBlending(x, endY, char, borderColor, backgroundColor);
}
}
}
}
const verticalStartY = extendVerticalsToTop ? startY : startY + (drawTop ? 1 : 0);
const verticalEndY = extendVerticalsToBottom ? endY : endY - (drawBottom ? 1 : 0);
if (drawLeft || drawRight) {
for (let y = verticalStartY;y <= verticalEndY; y++) {
if (drawLeft && startX >= 0 && startX < buffer.getWidth()) {
buffer.setCellWithAlphaBlending(startX, y, borders.vertical, borderColor, backgroundColor);
}
if (drawRight && endX >= 0 && endX < buffer.getWidth()) {
buffer.setCellWithAlphaBlending(endX, y, borders.vertical, borderColor, backgroundColor);
}
}
}
if (shouldDrawTitle && options.title) {
buffer.drawText(options.title, titleX, startY, borderColor, backgroundColor, 0);
}
}
// src/core/selection.ts
class Selection {
_anchor;
_focus;
_selectedRenderables = [];
constructor(anchor, focus) {
this._anchor = { ...anchor };
this._focus = { ...focus };
}
get anchor() {
return { ...this._anchor };
}
get focus() {
return { ...this._focus };
}
get bounds() {
return {
startX: Math.min(this._anchor.x, this._focus.x),
startY: Math.min(this._anchor.y, this._focus.y),
endX: Math.max(this._anchor.x, this._focus.x),
endY: Math.max(this._anchor.y, this._focus.y)
};
}
updateSelectedRenderables(selectedRenderables) {
this._selectedRenderables = selectedRenderables;
}
getSelectedText() {
const selectedTexts = this._selectedRenderables.sort((a, b) => {
const aY = a.y;
const bY = b.y;
if (aY !== bY) {
return aY - bY;
}
return a.x - b.x;
}).map((renderable) => renderable.getSelectedText()).filter((text) => text);
return selectedTexts.join(`
`);
}
}
class TextSelectionHelper {
getX;
getY;
getTextLength;
getLineInfo;
localSelection = null;
cachedGlobalSelection = null;
constructor(getX, getY, getTextLength, getLineInfo) {
this.getX = getX;
this.getY = getY;
this.getTextLength = getTextLength;
this.getLineInfo = getLineInfo;
}
hasSelection() {
return this.localSelection !== null;
}
getSelection() {
return this.localSelection;
}
reevaluateSelection(width, height = 1) {
if (!this.cachedGlobalSelection) {
return false;
}
return this.onSelectionChanged(this.cachedGlobalSelection, width, height);
}
shouldStartSelection(x, y, width, height) {
const localX = x - this.getX();
const localY = y - this.getY();
return localX >= 0 && localX < width && localY >= 0 && localY < height;
}
onSelectionChanged(selection, width, height = 1) {
this.cachedGlobalSelection = selection;
const previousSelection = this.localSelection;
if (!selection?.isActive) {
this.localSelection = null;
return previousSelection !== null;
}
const myY = this.getY();
const myEndY = myY + height - 1;
if (myEndY < selection.anchor.y || myY > selection.focus.y) {
this.localSelection = null;
return previousSelection !== null;
}
if (height === 1) {
this.localSelection = this.calculateSingleLineSelection(myY, selection.anchor.y, selection.focus.y, selection.anchor.x, selection.focus.x, width);
} else {
this.localSelection = this.calculateMultiLineSelection(myY, selection.anchor.y, selection.focus.y, selection.anchor.x, selection.focus.x);
}
return this.localSelection !== null !== (previousSelection !== null) || this.localSelection?.start !== previousSelection?.start || this.localSelection?.end !== previousSelection?.end;
}
calculateSingleLineSelection(lineY, anchorY, focusY, anchorX, focusX, width) {
const textLength = this.getTextLength();
const myX = this.getX();
if (lineY > anchorY && lineY < focusY) {
return { start: 0, end: textLength };
}
if (lineY === anchorY && lineY === focusY) {
const start = Math.max(0, Math.min(anchorX - myX, textLength));
const end = Math.max(0, Math.min(focusX - myX, textLength));
return start < end ? { start, end } : null;
}
if (lineY === anchorY) {
const start = Math.max(0, Math.min(anchorX - myX, textLength));
return start < textLength ? { start, end: textLength } : null;
}
if (lineY === focusY) {
const end = Math.max(0, Math.min(focusX - myX, textLength));
return end > 0 ? { start: 0, end } : null;
}
return null;
}
calculateMultiLineSelection(startY, anchorY, focusY, anchorX, focusX) {
const lineInfo = this.getLineInfo?.();
if (!lineInfo) {
return { start: 0, end: this.getTextLength() };
}
const myX = this.getX();
let selectionStart = null;
let selectionEnd = null;
for (let i = 0;i < lineInfo.lineStarts.length; i++) {
const lineY = startY + i;
if (lineY < anchorY || lineY > focusY)
continue;
const lineStart = lineInfo.lineStarts[i];
const lineEnd = i < lineInfo.lineStarts.length - 1 ? lineInfo.lineStarts[i + 1] - 1 : this.getTextLength();
const lineWidth = lineInfo.lineWidths[i];
if (lineY > anchorY && lineY < focusY) {
if (selectionStart === null)
selectionStart = lineStart;
selectionEnd = lineEnd;
} else if (lineY === anchorY && lineY === focusY) {
const localStartX = Math.max(0, Math.min(anchorX - myX, lineWidth));
const localEndX = Math.max(0, Math.min(focusX - myX, lineWidth));
if (localStartX < localEndX) {
selectionStart = lineStart + localStartX;
selectionEnd = lineStart + localEndX;
}
} else if (lineY === anchorY) {
const localStartX = Math.max(0, Math.min(anchorX - myX, lineWidth));
if (localStartX < lineWidth) {
selectionStart = lineStart + localStartX;
selectionEnd = lineEnd;
}
} else if (lineY === focusY) {
const localEndX = Math.max(0, Math.min(focusX - myX, lineWidth));
if (localEndX > 0) {
if (selectionStart === null)
selectionStart = lineStart;
selectionEnd = lineStart + localEndX;
}
}
}
return selectionStart !== null && selectionEnd !== null && selectionStart < selectionEnd ? { start: selectionStart, end: selectionEnd } : null;
}
}
// src/core/objects.ts
function sanitizeText(text, tabStopWidth) {
return text.replace(/\t/g, " ".repeat(tabStopWidth));
}
class TextRenderable extends Renderable {
selectable = true;
_content = "";
_fg;
_bg;
attributes = 0;
tabStopWidth = 2;
selectionHelper;
constructor(id, options) {
super(id, { ...options, width: 0, height: 0 });
const fgRgb = parseColor(options.fg || RGBA.fromInts(255, 255, 255, 255));
this.selectionHelper = new TextSelectionHelper(() => this.x, () => this.y, () => this._content.length);
this.tabStopWidth = options.tabStopWidth || 2;
this.setContent(options.content);
this._fg = fgRgb;
this._bg = options.bg !== undefined ? parseColor(options.bg) : RGBA.fromValues(0, 0, 0, 0);
this.attributes = options.attributes || 0;
}
setContent(value) {
this._content = sanitizeText(value, this.tabStopWidth);
this.width = this._content.length;
this.height = 1;
const changed = this.selectionHelper.reevaluateSelection(this.width);
if (changed) {
this.needsUpdate = true;
}
}
get fg() {
return this._fg;
}
get bg() {
return this._bg;
}
set fg(value) {
if (value) {
this._fg = parseColor(value);
this.needsUpdate = true;
}
}
set bg(value) {
if (value) {
this._bg = parseColor(value);
this.needsUpdate = true;
}
}
set content(value) {
this.setContent(value);
this.needsUpdate = true;
}
get content() {
return this._content;
}
shouldStartSelection(x, y) {
return this.selectionHelper.shouldStartSelection(x, y, this.width, this.height);
}
onSelectionChanged(selection) {
const changed = this.selectionHelper.onSelectionChanged(selection, this.width);
if (changed) {
this.needsUpdate = true;
}
return this.selectionHelper.hasSelection();
}
getSelectedText() {
const selection = this.selectionHelper.getSelection();
if (!selection)
return "";
return this._content.slice(selection.start, selection.end);
}
hasSelection() {
return this.selectionHelper.hasSelection();
}
renderSelf(buffer) {
const selection = this.selectionHelper.getSelection();
buffer.drawText(this._content, this.x, this.y, this._fg, this._bg, this.attributes, selection);
}
}
class BoxRenderable extends Renderable {
_bg;
_border;
_borderStyle;
borderColor;
customBorderChars;
borderSides;
shouldFill;
title;
titleAlignment;
constructor(id, options) {
super(id, options);
const bgRgb = parseColor(options.bg);
const borderRgb = parseColor(options.borderColor || RGBA.fromValues(255, 255, 255, 255));
this.width = options.width;
this.height = options.height;
this._bg = bgRgb;
this._border = options.border ?? true;
this._borderStyle = options.borderStyle || "single";
this.borderColor = borderRgb;
this.customBorderChars = options.customBorderChars || BorderChars[this._borderStyle];
this.borderSides = getBorderSides(this._border);
this.shouldFill = options.shouldFill !== false;
this.title = options.title;
this.titleAlignment = options.titleAlignment || "left";
}
get bg() {
return this._bg;
}
set bg(value) {
if (value) {
this._bg = parseColor(value);
}
}
set border(value) {
this._border = value;
this.borderSides = getBorderSides(value);
this.needsUpdate = true;
}
set borderStyle(value) {
this._borderStyle = value;
this.customBorderChars = BorderChars[this._borderStyle];
this.needsUpdate = true;
}
renderSelf(buffer) {
if (this.x >= buffer.getWidth() || this.y >= buffer.getHeight() || this.x + this.width <= 0 || this.y + this.height <= 0) {
return;
}
const startX = Math.max(0, this.x);
const startY = Math.max(0, this.y);
const endX = Math.min(buffer.getWidth() - 1, this.x + this.width - 1);
const endY = Math.min(buffer.getHeight() - 1, this.y + this.height - 1);
if (this.shouldFill) {
if (this.border === false) {
buffer.fillRect(startX, startY, endX - startX + 1, endY - startY + 1, this._bg);
} else {
const innerStartX = startX + (this.borderSides.left ? 1 : 0);
const innerStartY = startY + (this.borderSides.top ? 1 : 0);
const innerEndX = endX - (this.borderSides.right ? 1 : 0);
const innerEndY = endY - (this.borderSides.bottom ? 1 : 0);
if (innerEndX >= innerStartX && innerEndY >= innerStartY) {
buffer.fillRect(innerStartX, innerStartY, innerEndX - innerStartX + 1, innerEndY - innerStartY + 1, this._bg);
}
}
}
if (this.border !== false) {
drawBorder(buffer, {
x: this.x,
y: this.y,
width: this.width,
height: this.height,
borderStyle: this._borderStyle,
border: this._border,
borderColor: this.borderColor,
backgroundColor: this._bg,
customBorderChars: this.customBorderChars,
title: this.title,
titleAlignment: this.titleAlignment
});
}
}
}
class FrameBufferRenderable extends Renderable {
frameBuffer;
constructor(id, buffer, options) {
super(id, options);
this.frameBuffer = buffer;
}
renderSelf(buffer) {
buffer.drawFrameBuffer(this.x, this.y, this.frameBuffer);
}
destroySelf() {
this.frameBuffer.destroy();
}
}
class GroupRenderable extends Renderable {
constructor(id, options) {
super(id, { ...options, width: 0, height: 0 });
}
has(id) {
return this.renderableMap.has(id);
}
}
class StyledTextRenderable extends Renderable {
selectable = true;
frameBuffer;
_fragment;
_defaultFg;
_defaultBg;
_selectionBg;
_selectionFg;
selectionHelper;
_plainText = "";
_lineInfo = {
lineStarts: [],
lineWidths: []
};
constructor(id, buffer, options) {
super(id, options);
this.selectionHelper = new TextSelectionHelper(() => this.x, () => this.y, () => this._plainText.length, () => this._lineInfo);
this.frameBuffer = buffer;
this._fragment = options.fragment;
this._defaultFg = options.defaultFg ? parseColor(options.defaultFg) : RGBA.fromValues(1, 1, 1, 1);
this._defaultBg = options.defaultBg ? parseColor(options.defaultBg) : RGBA.fromValues(0, 0, 0, 0);
this._selectionBg = options.selectionBg ? parseColor(options.selectionBg) : undefined;
this._selectionFg = options.selectionFg ? parseColor(options.selectionFg) : undefined;
this.updateTextInfo();
this.renderFragmentToBuffer();
}
get fragment() {
return this._fragment;
}
set fragment(value) {
this._fragment = value;
this.updateTextInfo();
this.renderFragmentToBuffer();
this.needsUpdate = true;
}
get defaultFg() {
return this._defaultFg;
}
set defaultFg(value) {
if (value) {
this._defaultFg = parseColor(value);
this.renderFragmentToBuffer();
this.needsUpdate = true;
}
}
get defaultBg() {
return this._defaultBg;
}
set defaultBg(value) {
if (value) {
this._defaultBg = parseColor(value);
this.renderFragmentToBuffer();
this.needsUpdate = true;
}
}
updateTextInfo() {
this._plainText = this._fragment.toString();
this._lineInfo.lineStarts = [0];
this._lineInfo.lineWidths = [];
let currentLineWidth = 0;
for (let i = 0;i < this._plainText.length; i++) {
if (this._plainText[i] === `
`) {
this._lineInfo.lineWidths.push(currentLineWidth);
this._lineInfo.lineStarts.push(i + 1);
currentLineWidth = 0;
} else {
currentLineWidth++;
}
}
this._lineInfo.lineWidths.push(currentLineWidth);
const changed = this.selectionHelper.reevaluateSelection(this.width, this.height);
if (changed) {
this.renderFragmentToBuffer();
this.needsUpdate = true;
}
}
shouldStartSelection(x, y) {
return this.selectionHelper.shouldStartSelection(x, y, this.width, this.height);
}
onSelectionChanged(selection) {
const changed = this.selectionHelper.onSelectionChanged(selection, this.width, this.height);
if (changed) {
this.renderFragmentToBuffer();
this.needsUpdate = true;
}
return this.selectionHelper.hasSelection();
}
getSelectedText() {
const selection = this.selectionHelper.getSelection();
if (!selection)
return "";
return this._plainText.slice(selection.start, selection.end);
}
hasSelection() {
return this.selectionHelper.hasSelection();
}
renderFragmentToBuffer() {
this.frameBuffer.clear(this._defaultBg);
const selection = this.selectionHelper.getSelection();
this.frameBuffer.drawStyledTextFragment(this._fragment, 0, 0, this._defaultFg, this._defaultBg, selection ? { ...selection, bgColor: this._selectionBg, fgColor: this._selectionFg } : undefined);
}
renderSelf(buffer) {
buffer.drawFrameBuffer(this.x, this.y, this.frameBuffer);
}
destroySelf() {
this.frameBuffer.destroy();
}
}
// src/core/zig.ts
import { dlopen, suffix, toArrayBuffer } from "bun:ffi";
import { join } from "path";
import { existsSync } from "fs";
import os from "os";
// src/core/buffer.ts
var fbIdCounter = 0;
function isRGBAWithAlpha(color) {
return color.a < 1;
}
function blendColors(overlay, text) {
const [overlayR, overlayG, overlayB, overlayA] = overlay.buffer;
const [textR, textG, textB, textA] = text.buffer;
if (overlayA === 1) {
return overlay;
}
const alpha = overlayA;
let perceptualAlpha;
if (alpha > 0.8) {
const normalizedHighAlpha = (alpha - 0.8) * 5;
const curvedHighAlpha = Math.pow(normalizedHighAlpha, 0.2);
perceptualAlpha = 0.8 + curvedHighAlpha * 0.2;
} else {
perceptualAlpha = Math.pow(alpha, 0.9);
}
const r = overlayR * perceptualAlpha + textR * (1 - perceptualAlpha);
const g = overlayG * perceptualAlpha + textG * (1 - perceptualAlpha);
const b = overlayB * perceptualAlpha + textB * (1 - perceptualAlpha);
return RGBA.fromValues(r, g, b, textA);
}
class OptimizedBuffer {
id;
lib;
bufferPtr;
buffer;
width;
height;
respectAlpha = false;
useFFI = true;
get ptr() {
return this.bufferPtr;
}
constructor(lib, ptr, buffer, width, height, options) {
this.id = `fb_${fbIdCounter++}`;
this.lib = lib;
this.respectAlpha = options.respectAlpha || false;
this.width = width;
this.height = height;
this.bufferPtr = ptr;
this.buffer = buffer;
}
static create(width, height, options = {}) {
const lib = resolveRenderLib();
const respectAlpha = options.respectAlpha || false;
return lib.createOptimizedBuffer(width, height, respectAlpha);
}
get buffers() {
return this.buffer;
}
coordsToIndex(x, y) {
return y * this.width + x;
}
getWidth() {
return this.width;
}
getHeight() {
return this.height;
}
setRespectAlpha(respectAlpha) {
this.lib.bufferSetRespectAlpha(this.bufferPtr, respectAlpha);
this.respectAlpha = respectAlpha;
}
clear(bg = RGBA.fromValues(0, 0, 0, 1)) {
if (this.useFFI) {
this.clearFFI(bg);
} else {
this.clearLocal(bg);
}
}
clearLocal(bg = RGBA.fromValues(0, 0, 0, 1)) {
this.buffer.char.fill(32);
this.buffer.attributes.fill(0);
for (let i = 0;i < this.width * this.height; i++) {
const index = i * 4;
this.buffer.fg[index] = 1;
this.buffer.fg[index + 1] = 1;
this.buffer.fg[index + 2] = 1;
this.buffer.fg[index + 3] = 1;
this.buffer.bg[index] = bg.r;
this.buffer.bg[index + 1] = bg.g;
this.buffer.bg[index + 2] = bg.b;
this.buffer.bg[index + 3] = bg.a;
}
}
setCell(x, y, char, fg, bg, attributes = 0) {
if (x < 0 || x >= this.width || y < 0 || y >= this.height)
return;
const index = this.coordsToIndex(x, y);
const colorIndex = index * 4;
this.buffer.char[index] = typeof char === "string" ? char.charCodeAt(0) : char;
this.buffer.attributes[index] = attributes;
this.buffer.fg[colorIndex] = fg.r;
this.buffer.fg[colorIndex + 1] = fg.g;
this.buffer.fg[colorIndex + 2] = fg.b;
this.buffer.fg[colorIndex + 3] = fg.a;
this.buffer.bg[colorIndex] = bg.r;
this.buffer.bg[colorIndex + 1] = bg.g;
this.buffer.bg[colorIndex + 2] = bg.b;
this.buffer.bg[colorIndex + 3] = bg.a;
}
getCell(x, y) {
return this.get(x, y);
}
get(x, y) {
if (x < 0 || x >= this.width || y < 0 || y >= this.height)
return null;
const index = this.coordsToIndex(x, y);
const colorIndex = index * 4;
return {
char: this.buffer.char[index],
fg: RGBA.fromArray(this.buffer.fg.slice(colorIndex, colorIndex + 4)),
bg: RGBA.fromArray(this.buffer.bg.slice(colorIndex, colorIndex + 4)),
attributes: this.buffer.attributes[index]
};
}
setCellWithAlphaBlending(x, y, char, fg, bg, attributes = 0) {
if (this.useFFI) {
this.setCellWithAlphaBlendingFFI(x, y, char, fg, bg, attributes);
} else {
this.setCellWithAlphaBlendingLocal(x, y, char, fg, bg, attributes);
}
}
setCellWithAlphaBlendingLocal(x, y, char, fg, bg, attributes = 0) {
if (x < 0 || x >= this.width || y < 0 || y >= this.height)
return;
const hasBgAlpha = isRGBAWithAlpha(bg);
const hasFgAlpha = isRGBAWithAlpha(fg);
if (hasBgAlpha || hasFgAlpha) {
const destCell = this.get(x, y);
if (destCell) {
const blendedBgRgb = hasBgAlpha ? blendColors(bg, destCell.bg) : bg;
const preserveChar = char === " " && destCell.char !== 0 && String.fromCharCode(destCell.char) !== " ";
const finalChar = preserveChar ? destCell.char : char.charCodeAt(0);
let finalFg;
if (preserveChar) {
finalFg = blendColors(bg, destCell.fg);
} else {
finalFg = hasFgAlpha ? blendColors(fg, destCell.bg) : fg;
}
const finalAttributes = preserveChar ? destCell.attributes : attributes;
const finalBg = RGBA.fromValues(blendedBgRgb.r, blendedBgRgb.g, blendedBgRgb.b, bg.a);
this.setCell(x, y, String.fromCharCode(finalChar), finalFg, finalBg, finalAttributes);
return;
}
}
this.setCell(x, y, char, fg, bg, attributes);
}
drawText(text, x, y, fg, bg, attributes = 0, selection) {
const method = this.useFFI ? this.drawTextFFI : this.drawTextLocal;
if (!selection) {
method.call(this, text, x, y, fg, bg, attributes);
return;
}
const { start, end } = selection;
let selectionBg;
let selectionFg;
if (selection.bgColor) {
selectionBg = selection.bgColor;
selectionFg = selection.fgColor || fg;
} else {
const defaultBg = bg || RGBA.fromValues(0, 0, 0, 0);
selectionFg = defaultBg.a > 0 ? defaultBg : RGBA.fromValues(0, 0, 0, 1);
selectionBg = fg;
}
if (start > 0) {
const beforeText = text.slice(0, start);
method.call(this, beforeText, x, y, fg, bg, attributes);
}
if (end > start) {
const selectedText = text.slice(start, end);
method.call(this, selectedText, x + start, y, selectionFg, selectionBg, attributes);
}
if (end < text.length) {
const afterText = text.slice(end);
method.call(this, afterText, x + end, y, fg, bg, attributes);
}
}
drawTextLocal(text, x, y, fg, bg, attributes = 0) {
if (y < 0 || y >= this.height)
return;
if (!text || typeof text !== "string") {
console.warn("drawTextLocal called with invalid text:", { text, x, y, fg, bg });
return;
}
let startX = this.width;
let endX = 0;
let i = 0;
for (const char of text) {
const charX = x + i;
i++;
if (charX < 0 || charX >= this.width)
continue;
startX = Math.min(startX, charX);
endX = Math.max(endX, charX);
let bgColor = bg;
if (!bgColor) {
const existingCell = this.get(charX, y);
if (existingCell) {
bgColor = existingCell.bg;
} else {
bgColor = RGBA.fromValues(0, 0, 0, 1);
}
}
this.setCellWithAlphaBlending(charX, y, char, fg, bgColor, attributes);
}
}
fillRect(x, y, width, height, bg) {
if (this.useFFI) {
this.fillRectFFI(x, y, width, height, bg);
} else {
this.fillRectLocal(x, y, width, height, bg);
}
}
fillRectLocal(x, y, width, height, bg) {
const startX = Math.max(0, x);
const startY = Math.max(0, y);
const endX = Math.min(this.getWidth() - 1, x + width - 1);
const endY = Math.min(this.getHeight() - 1, y + height - 1);
if (startX > endX || startY > endY)
return;
const hasAlpha = isRGBAWithAlpha(bg);
if (hasAlpha) {
const fg = RGBA.fromValues(1, 1, 1, 1);
for (let fillY = startY;fillY <= endY; fillY++) {
for (let fillX = startX;fillX <= endX; fillX++) {
this.setCellWithAlphaBlending(fillX, fillY, " ", fg, bg, 0);
}
}
} else {
for (let fillY = startY;fillY <= endY; fillY++) {
for (let fillX = startX;fillX <= endX; fillX++) {
const index = this.coordsToIndex(fillX, fillY);
const colorIndex = index * 4;
this.buffer.char[index] = 32;
this.buffer.attributes[index] = 0;
this.buffer.fg[colorIndex] = 1;
this.buffer.fg[colorIndex + 1] = 1;
this.buffer.fg[colorIndex + 2] = 1;
this.buffer.fg[colorIndex + 3] = 1;
this.buffer.bg[colorIndex] = bg.r;
this.buffer.bg[colorIndex + 1] = bg.g;
this.buffer.bg[colorIndex + 2] = bg.b;
this.buffer.bg[colorIndex + 3] = bg.a;
}
}
}
}
drawFrameBuffer(destX, destY, frameBuffer, sourceX, sourceY, sourceWidth, sourceHeight) {
this.drawFrameBufferFFI(destX, destY, frameBuffer, sourceX, sourceY, sourceWidth, sourceHeight);
}
drawFrameBufferLocal(destX, destY, frameBuffer, sourceX, sourceY, sourceWidth, sourceHeight) {
const srcX = sourceX ?? 0;
const srcY = sourceY ?? 0;
const srcWidth = sourceWidth ?? frameBuffer.getWidth();
const srcHeight = sourceHeight ?? frameBuffer.getHeight();
if (srcX >= frameBuffer.getWidth() || srcY >= frameBuffer.getHeight())
return;
if (srcWidth === 0 || srcHeight === 0)
return;
const clampedSrcWidth = Math.min(srcWidth, frameBuffer.getWidth() - srcX);
const clampedSrcHeight = Math.min(srcHeight, frameBuffer.getHeight() - srcY);
const startDestX = Math.max(0, destX);
const startDestY = Math.max(0, destY);
const endDestX = Math.min(this.width - 1, destX + clampedSrcWidth - 1);
const endDestY = Math.min(this.height - 1, destY + clampedSrcHeight - 1);
if (!frameBuffer.respectAlpha) {
for (let dY = startDestY;dY <= endDestY; dY++) {
for (let dX = startDestX;dX <= endDestX; dX++) {
const relativeDestX = dX - destX;
const relativeDestY = dY - destY;
const sX = srcX + relativeDestX;
const sY = srcY + relativeDestY;
if (sX >= frameBuffer.getWidth() || sY >= frameBuffer.getHeight())
continue;
const destIndex = this.coordsToIndex(dX, dY);
const srcIndex = frameBuffer.coordsToIndex(sX, sY);
const destColorIndex = destIndex * 4;
const srcColorIndex = srcIndex * 4;
this.buffer.char[destIndex] = frameBuffer.buffer.char[srcIndex];
this.buffer.attributes[destIndex] = frameBuffer.buffer.attributes[srcIndex];
this.buffer.fg[destColorIndex] = frameBuffer.buffer.fg[srcColorIndex];
this.buffer.fg[destColorIndex + 1] = frameBuffer.buffer.fg[srcColorIndex + 1];
this.buffer.fg[destColorIndex + 2] = frameBuffer.buffer.fg[srcColorIndex + 2];
this.buffer.fg[destColorIndex + 3] = frameBuffer.buffer.fg[srcColorIndex + 3];
this.buffer.bg[destColorIndex] = frameBuffer.buffer.bg[srcColorIndex];
this.buffer.bg[destColorIndex + 1] = frameBuffer.buffer.bg[srcColorIndex + 1];
this.buffer.bg[destColorIndex + 2] = frameBuffer.buffer.bg[srcColorIndex + 2];
this.buffer.bg[destColorIndex + 3] = frameBuffer.buffer.bg[srcColorIndex + 3];
}
}
return;
}
for (let dY = startDestY;dY <= endDestY; dY++) {
for (let dX = startDestX;dX <= endDestX; dX++) {
const relativeDestX = dX - destX;
const relativeDestY = dY - destY;
const sX = srcX + relativeDestX;
const sY = srcY + relativeDestY;
if (sX >= frameBuffer.getWidth() || sY >= frameBuffer.getHeight())
continue;
const srcIndex = frameBuffer.coordsToIndex(sX, sY);
const srcColorIndex = srcIndex * 4;
if (frameBuffer.buffer.bg[srcColorIndex + 3] === 0 && frameBuffer.buffer.fg[srcColorIndex + 3] === 0) {
continue;
}
const charCode = frameBuffer.buffer.char[srcIndex];
const fg = RGBA.fromArray(frameBuffer.buffer.fg.slice(srcColorIndex, srcColorIndex + 4));
const bg = RGBA.fromArray(frameBuffer.buffer.bg.slice(srcColorIndex, srcColorIndex + 4));
const attributes = frameBuffer.buffer.attributes[srcIndex];
this.setCellWithAlphaBlending(dX, dY, String.fromCharCode(charCode), fg, bg, attributes);
}
}
}
destroy() {
this.lib.destroyOptimizedBuffer(this.bufferPtr);
}
drawStyledText(styledText, x, y, defaultFg = RGBA.fromValues(1, 1, 1, 1), defaultBg = RGBA.fromValues(0, 0, 0, 0)) {
this.drawStyledTextLocal(styledText, x, y, defaultFg, defaultBg);
}
drawStyledTextLocal(styledText, x, y, defaultFg = RGBA.fromValues(1, 1, 1, 1), defaultBg = RGBA.fromValues(0, 0, 0, 0), selection) {
let currentX = x;
let currentY = y;
let charIndex = 0;
for (const styledChar of styledText) {
if (styledChar.char === `
`) {
currentY++;
currentX = x;
charIndex++;
continue;
}
let fg = styledChar.style.fg ? parseColor(styledChar.style.fg) : defaultFg;
let bg = styledChar.style.bg ? parseColor(styledChar.style.bg) : defaultBg;
const isSelected = selection && charIndex >= selection.start && charIndex < selection.end;
if (isSelected) {
if (selection.bgColor) {
bg = selection.bgColor;
if (selection.fgColor) {
fg = selection.fgColor;
}
} else {
const temp = fg;
fg = bg.a > 0 ? bg : RGBA.fromValues(0, 0, 0, 1);
bg = temp;
}
}
if (styledChar.style.reverse) {
[fg, bg] = [bg, fg];
}
const attributes = createTextAttributes({
bold: styledChar.style.bold,
italic: styledChar.style.italic,
underline: styledChar.style.underline,
dim: styledChar.style.dim,
blink: styledChar.style.blink,
inverse: styledChar.style.reverse,
hidden: false,
strikethrough: styledChar.style.strikethrough
});
this.setCellWithAlphaBlending(currentX, currentY, styledChar.char, fg, bg, attributes);
currentX++;
charIndex++;
}
}
drawStyledTextFragment(fragment, x, y, defaultFg, defaultBg, selection) {
this.drawStyledTextFragmentLocal(fragment, x, y, defaultFg, defaultBg, selection);
}
drawStyledTextFragmentLocal(fragment, x, y, defaultFg, defaultBg, selection) {
this.drawStyledTextLocal(fragment.toStyledText(), x, y, defaultFg, defaultBg, selection);
}
drawSuperSampleBuffer(x, y, pixelDataPtr, pixelDataLength, format, alignedBytesPerRow) {
this.drawSuperSampleBufferFFI(x, y, pixelDataPtr, pixelDataLength, format, alignedBytesPerRow);
}
drawSuperSampleBufferFFI(x, y, pixelDataPtr, pixelDataLength, format, alignedBytesPerRow) {
this.lib.bufferDrawSuperSampleBuffer(this.bufferPtr, x, y, pixelDataPtr, pixelDataLength, format, alignedBytesPerRow);
}
drawPackedBuffer(dataPtr, dataLen, posX, posY, terminalWidthCells, terminalHeightCells) {
this.lib.bufferDrawPackedBuffer(this.bufferPtr, dataPtr, dataLen, posX, posY, terminalWidthCells, terminalHeightCells);
}
setCellWithAlphaBlendingFFI(x, y, char, fg, bg, attributes) {
this.lib.bufferSetCellWithAlphaBlending(this.bufferPtr, x, y, char, fg, bg, attributes);
}
fillRectFFI(x, y, width, height, bg) {
this.lib.bufferFillRect(this.bufferPtr, x, y, width, height, bg);
}
resize(width, height) {
if (this.width === width && this.height === height)
return;
this.width = width;
this.height = height;
this.buffer = this.lib.bufferResize(this.bufferPtr, width, height);
}
clearFFI(bg = RGBA.fromValues(0, 0, 0, 1)) {
this.lib.bufferClear(this.bufferPtr, bg);
}
drawTextFFI(text, x, y, fg = RGBA.fromValues(1, 1, 1, 1), bg, attributes = 0) {
this.lib.bufferDrawText(this.bufferPtr, text, x, y, fg, bg, attributes);
}
drawFrameBufferFFI(destX, destY, frameBuffer, sourceX, sourceY, sourceWidth, sourceHeight) {
this.lib.drawFrameBuffer(this.bufferPtr, destX, destY, frameBuffer.ptr, sourceX, sourceY, sourceWidth, sourceHeight);
}
}
// src/core/zig.ts
var __dirname = "G:\\code\\shellquest\\game\\src\\core";
function getPlatformTarget() {
const platform = os.platform();
const arch = os.arch();
const platformMap = {
darwin: "macos",
win32: "windows",
linux: "linux"
};
const archMap = {
x64: "x86_64",
arm64: "aarch64"
};
const zigPlatform = platformMap[platform] || platform;
const zigArch = archMap[arch] || arch;
return `${zigArch}-${zigPlatform}`;
}
function findLibrary() {
const target = getPlatformTarget();
const [arch, os2] = target.split("-");
const isWindows = os2 === "windows";
const libraryName = isWindows ? "opentui" : "libopentui";
let entryDir;
try {
entryDir = typeof Bun !== "undefined" && Bun.main ? __require("path").dirname(Bun.main) : undefined;
} catch {}
if (!entryDir && process.argv[1]) {
entryDir = __require("path").dirname(process.argv[1]);
}
if (entryDir) {
const candidate = join(entryDir, "dist", "zig", "lib", target, `${libraryName}.${suffix}`);
if (existsSync(candidate)) {
return candidate;
}
let dir = entryDir;
for (let i = 0;i < 4; ++i) {
dir = __require("path").dirname(dir);
const candidate2 = join(dir, "dist", "zig", "lib", target, `${libraryName}.${suffix}`);
if (existsSync(candidate2)) {
return candidate2;
}
}
}
const possiblePaths = [
join(process.cwd(), "dist", "zig", "lib", target),
join(process.cwd(), "node_modules", "shellquest.sh", "dist", "zig", "lib", target),
join(process.cwd(), "node_modules", "shellquest", "dist", "zig", "lib", target),
join(process.cwd(), "node_modules", "tui-crawler", "dist", "zig", "lib", target),
join(process.cwd(), "src", "zig", "lib", target),
join(__dirname, "zig", "lib", target),
join(__dirname, "..", "zig", "lib", target),
join(__dirname, "..", "..", "zig", "lib", target)
];
for (const basePath of possiblePaths) {
const targetLibPath = join(basePath, `${libraryName}.${suffix}`);
if (existsSync(targetLibPath)) {
return targetLibPath;
}
}
throw new Error(`Could not find opentui library for platform: ${target}`);
}
function getOpenTUILib(libPath) {
const resolvedLibPath = libPath || findLibrary();
return dlopen(resolvedLibPath, {
createRenderer: {
args: ["u32", "u32"],
returns: "ptr"
},
destroyRenderer: {
args: ["ptr"],
returns: "void"
},
setUseThread: {
args: ["ptr", "bool"],
returns: "void"
},
setBackgroundColor: {
args: ["ptr", "ptr"],
returns: "void"
},
updateStats: {
args: ["ptr", "f64", "u32", "f64"],
returns: "void"
},
updateMemoryStats: {
args: ["ptr", "u32", "u32", "u32"],
returns: "void"
},
render: {
args: ["ptr"],
returns: "void"
},
getNextBuffer: {
args: ["ptr"],
returns: "ptr"
},
getCurrentBuffer: {
args: ["ptr"],
returns: "ptr"
},
createOptimizedBuffer: {
args: ["u32", "u32", "bool"],
returns: "ptr"
},
destroyOptimizedBuffer: {
args: ["ptr"],
returns: "void"
},
drawFrameBuffer: {
args: ["ptr", "i32", "i32", "ptr", "u32", "u32", "u32", "u32"],
returns: "void"
},
getBufferWidth: {
args: ["ptr"],
returns: "u32"
},
getBufferHeight: {
args: ["ptr"],
returns: "u32"
},
bufferClear: {
args: ["ptr", "ptr"],
returns: "void"
},
bufferGetCharPtr: {
args: ["ptr"],
returns: "ptr"
},
bufferGetFgPtr: {
args: ["ptr"],
returns: "ptr"
},
bufferGetBgPtr: {
args: ["ptr"],
returns: "ptr"