From a571d6cc2a6cc0fee47c389f28737ec41fa938e9 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Fri, 8 May 2026 15:52:48 +0000 Subject: [PATCH] Add alignment to text display --- src/common/display/text.ts | 69 +++++++++++++++++++++++---- src/common/rpg/systems/render/text.ts | 4 +- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/common/display/text.ts b/src/common/display/text.ts index 7f50e92..1d31b8f 100644 --- a/src/common/display/text.ts +++ b/src/common/display/text.ts @@ -138,6 +138,11 @@ export enum Glyphs { DIAMOND = '♦', } +export enum AlignHorizontal { + LEFT = 'left', + RIGHT = 'right', +} + 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 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, ].includes(char as Glyphs); -interface BoxOptions { +interface StringOptions { + fg?: ColorLike; + bg?: ColorLike; + alignH?: AlignHorizontal; + anchorH?: AlignHorizontal; +} + +interface BoxOptions extends StringOptions { vertical?: string; horizontal?: string; topLeft?: string; @@ -153,8 +165,6 @@ interface BoxOptions { bottomLeft?: string; bottomRight?: string; fill?: Char; - fg?: ColorLike; - bg?: ColorLike; 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]]; } - setRegion(x: number, y: number, region: TextRegion) { + setRegion(region: TextRegion, x: number, y: number, anchorH = AlignHorizontal.LEFT) { x = x | 0; y = y | 0; + + if (anchorH === AlignHorizontal.RIGHT) { + x -= region.width + 1; + } + const { chars, fgs, bgs } = region[REGION_DATA]; const rw = region.width; const x0 = Math.max(this.clipLeft, x); @@ -375,15 +390,31 @@ export class TextDisplay { 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; + const { + fg = DEFAULT_FG, + bg = DEFAULT_BG, + alignH = AlignHorizontal.LEFT, + anchorH = AlignHorizontal.LEFT, + } = options; 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++) { - const line = lines[row]; + let line = lines[row]; const ry = y + row; 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++) { - 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, fill, title, + anchorH = AlignHorizontal.LEFT, } = options; + if (anchorH === AlignHorizontal.RIGHT) { + x -= width + 1; + } + this.setCharRaw(x, y, topLeft, fg, bg); this.setCharRaw(x + width + 1, y, topRight, 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]); 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 { fg = DEFAULT_FG, bg = DEFAULT_BG, + alignH = AlignHorizontal.LEFT, + anchorH = AlignHorizontal.LEFT, + title, } = options; 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; + 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.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) { diff --git a/src/common/rpg/systems/render/text.ts b/src/common/rpg/systems/render/text.ts index 79a008f..a0646ee 100644 --- a/src/common/rpg/systems/render/text.ts +++ b/src/common/rpg/systems/render/text.ts @@ -42,11 +42,11 @@ export class TextDisplaySystem extends System { const region = data instanceof TextRegion ? data : new TextRegion(data); if (absolute || !offset || !viewportClipRect) { - this.display.setRegion(x, y, region); + this.display.setRegion(region, x, y); } else { const clipRect = this.display.getClipRect(); 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); } }