1
0
Fork 0

Add alignment to text display

This commit is contained in:
Pabloader 2026-05-08 15:52:48 +00:00
parent 2bedecb677
commit a571d6cc2a
2 changed files with 61 additions and 12 deletions

View File

@ -138,6 +138,11 @@ export enum Glyphs {
DIAMOND = '♦', DIAMOND = '♦',
} }
export enum AlignHorizontal {
LEFT = 'left',
RIGHT = 'right',
}
export const isVertical = (char: string) => char === Glyphs.SINGLE_VERTICAL || char === Glyphs.DOUBLE_VERTICAL; export const isVertical = (char: string) => char === Glyphs.SINGLE_VERTICAL || char === Glyphs.DOUBLE_VERTICAL;
export const isHorizontal = (char: string) => char === Glyphs.SINGLE_HORIZONTAL || char === Glyphs.DOUBLE_HORIZONTAL; export const isHorizontal = (char: string) => char === Glyphs.SINGLE_HORIZONTAL || char === Glyphs.DOUBLE_HORIZONTAL;
export const isCorner = (char: string) => [ export const isCorner = (char: string) => [
@ -145,7 +150,14 @@ export const isCorner = (char: string) => [
Glyphs.DOUBLE_TOP_LEFT, Glyphs.DOUBLE_TOP_RIGHT, Glyphs.DOUBLE_BOTTOM_LEFT, Glyphs.DOUBLE_BOTTOM_RIGHT, Glyphs.DOUBLE_TOP_LEFT, Glyphs.DOUBLE_TOP_RIGHT, Glyphs.DOUBLE_BOTTOM_LEFT, Glyphs.DOUBLE_BOTTOM_RIGHT,
].includes(char as Glyphs); ].includes(char as Glyphs);
interface BoxOptions { interface StringOptions {
fg?: ColorLike;
bg?: ColorLike;
alignH?: AlignHorizontal;
anchorH?: AlignHorizontal;
}
interface BoxOptions extends StringOptions {
vertical?: string; vertical?: string;
horizontal?: string; horizontal?: string;
topLeft?: string; topLeft?: string;
@ -153,8 +165,6 @@ interface BoxOptions {
bottomLeft?: string; bottomLeft?: string;
bottomRight?: string; bottomRight?: string;
fill?: Char; fill?: Char;
fg?: ColorLike;
bg?: ColorLike;
title?: string; title?: string;
} }
@ -313,9 +323,14 @@ export class TextDisplay {
return [this.chars[y * this.width + x], this.fgs[y * this.width + x], this.bgs[y * this.width + x]]; return [this.chars[y * this.width + x], this.fgs[y * this.width + x], this.bgs[y * this.width + x]];
} }
setRegion(x: number, y: number, region: TextRegion) { setRegion(region: TextRegion, x: number, y: number, anchorH = AlignHorizontal.LEFT) {
x = x | 0; x = x | 0;
y = y | 0; y = y | 0;
if (anchorH === AlignHorizontal.RIGHT) {
x -= region.width + 1;
}
const { chars, fgs, bgs } = region[REGION_DATA]; const { chars, fgs, bgs } = region[REGION_DATA];
const rw = region.width; const rw = region.width;
const x0 = Math.max(this.clipLeft, x); const x0 = Math.max(this.clipLeft, x);
@ -375,15 +390,31 @@ export class TextDisplay {
return new TextRegion(data); return new TextRegion(data);
} }
drawString(text: unknown, x: number, y: number, fg: ColorLike = DEFAULT_FG, bg: ColorLike = DEFAULT_BG) { drawString(
text: unknown, x: number, y: number,
options: StringOptions,
) {
x = x | 0; y = y | 0; x = x | 0; y = y | 0;
const {
fg = DEFAULT_FG,
bg = DEFAULT_BG,
alignH = AlignHorizontal.LEFT,
anchorH = AlignHorizontal.LEFT,
} = options;
const lines = String(text).split('\n'); const lines = String(text).split('\n');
const maxWidth = lines.reduce((m, line) => Math.max(m, line.length), 0);
if (anchorH === AlignHorizontal.RIGHT) {
x -= maxWidth - 1;
}
for (let row = 0; row < lines.length; row++) { for (let row = 0; row < lines.length; row++) {
const line = lines[row]; let line = lines[row];
const ry = y + row; const ry = y + row;
if (ry < this.clipTop || ry >= this.clipBottom) continue; if (ry < this.clipTop || ry >= this.clipBottom) continue;
const offset = alignH === AlignHorizontal.RIGHT ? maxWidth - line.length : 0;
for (let col = 0; col < line.length; col++) { for (let col = 0; col < line.length; col++) {
this.setCharRaw(x + col, ry, line[col], fg, bg); this.setCharRaw(x + col + offset, ry, line[col], fg, bg);
} }
} }
} }
@ -428,8 +459,13 @@ export class TextDisplay {
bg = DEFAULT_BG, bg = DEFAULT_BG,
fill, fill,
title, title,
anchorH = AlignHorizontal.LEFT,
} = options; } = options;
if (anchorH === AlignHorizontal.RIGHT) {
x -= width + 1;
}
this.setCharRaw(x, y, topLeft, fg, bg); this.setCharRaw(x, y, topLeft, fg, bg);
this.setCharRaw(x + width + 1, y, topRight, fg, bg); this.setCharRaw(x + width + 1, y, topRight, fg, bg);
this.setCharRaw(x, y + height + 1, bottomLeft, fg, bg); this.setCharRaw(x, y + height + 1, bottomLeft, fg, bg);
@ -446,7 +482,7 @@ export class TextDisplay {
this.drawVLine(x + width + 1, y + 1, y + height, [vertical, fg, bg]); this.drawVLine(x + width + 1, y + 1, y + height, [vertical, fg, bg]);
if (title) { if (title) {
this.drawString(title, x + 1, y, fg, bg); this.drawString(title, x + 1, y, { fg, bg });
} }
} }
@ -455,13 +491,26 @@ export class TextDisplay {
const { const {
fg = DEFAULT_FG, fg = DEFAULT_FG,
bg = DEFAULT_BG, bg = DEFAULT_BG,
alignH = AlignHorizontal.LEFT,
anchorH = AlignHorizontal.LEFT,
title,
} = options; } = options;
const lines = String(text).split('\n'); const lines = String(text).split('\n');
const width = lines.reduce((m, line) => Math.max(m, line.length), 0); const textWidth = lines.reduce((m, line) => Math.max(m, line.length), 0);
const height = lines.length; const height = lines.length;
let width = textWidth;
if (title) {
width = Math.max(width, title.length);
}
let dx = anchorH === AlignHorizontal.RIGHT ? -1 : 1;
if (alignH === AlignHorizontal.LEFT && anchorH === AlignHorizontal.RIGHT) {
dx -= width - textWidth;
}
this.drawBox(x, y, width, height, { ...options, fill: ' ' }); this.drawBox(x, y, width, height, { ...options, fill: ' ' });
this.drawString(text, x + 1, y + 1, fg, bg); this.drawString(text, x + dx, y + 1, { fg, bg, alignH, anchorH });
} }
fillBox(x: number, y: number, width: number, height: number, char: Char = Glyphs.FULL_BLOCK) { fillBox(x: number, y: number, width: number, height: number, char: Char = Glyphs.FULL_BLOCK) {

View File

@ -42,11 +42,11 @@ export class TextDisplaySystem extends System {
const region = data instanceof TextRegion ? data : new TextRegion(data); const region = data instanceof TextRegion ? data : new TextRegion(data);
if (absolute || !offset || !viewportClipRect) { if (absolute || !offset || !viewportClipRect) {
this.display.setRegion(x, y, region); this.display.setRegion(region, x, y);
} else { } else {
const clipRect = this.display.getClipRect(); const clipRect = this.display.getClipRect();
this.display.setClipRect(viewportClipRect); this.display.setClipRect(viewportClipRect);
this.display.setRegion(x - offset.x, y - offset.y, region); this.display.setRegion(region, x - offset.x, y - offset.y);
this.display.setClipRect(clipRect); this.display.setClipRect(clipRect);
} }
} }