[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
5

Configure Feed

Select the types of activity you want to include in your feed.

feat(border): add style support

Nate Moore (May 26, 2026, 7:15 PM EDT) 0de23e5a 819da8ee

+109 -9
+8
ops.ts
··· 146 146 true, 147 147 ); 148 148 o += 4; 149 + const styleMap: Record<string, number> = { 150 + single: 0, 151 + double: 1, 152 + bold: 2, 153 + }; 154 + view.setUint32(o, styleMap[b.style ?? "single"] ?? 0, true); 155 + o += 4; 149 156 } 150 157 151 158 if (op.clip) { ··· 259 266 right?: number; 260 267 top?: number; 261 268 bottom?: number; 269 + style?: "single" | "double" | "bold"; 262 270 }; 263 271 clip?: { horizontal?: boolean; vertical?: boolean }; 264 272 floating?: {
+24 -9
src/clayterm.c
··· 305 305 int bot = b->width.bottom > 0; 306 306 int left = b->width.left > 0; 307 307 int right = b->width.right > 0; 308 + uint8_t style = (uint8_t)b->width.betweenChildren; 308 309 309 - /* corners — rounded when corner radius > 0 */ 310 - uint32_t tl = b->cornerRadius.topLeft > 0 ? 0x256d : 0x250c; 311 - uint32_t tr = b->cornerRadius.topRight > 0 ? 0x256e : 0x2510; 312 - uint32_t bl = b->cornerRadius.bottomLeft > 0 ? 0x2570 : 0x2514; 313 - uint32_t br = b->cornerRadius.bottomRight > 0 ? 0x256f : 0x2518; 310 + uint32_t h_char, v_char, tl, tr, bl, br; 311 + switch (style) { 312 + case 1: /* double */ 313 + tl = 0x2554; tr = 0x2557; bl = 0x255a; br = 0x255d; 314 + h_char = 0x2550; v_char = 0x2551; 315 + break; 316 + case 2: /* bold */ 317 + tl = 0x250f; tr = 0x2513; bl = 0x2517; br = 0x251b; 318 + h_char = 0x2501; v_char = 0x2503; 319 + break; 320 + default: /* single — corners rounded when cornerRadius > 0 */ 321 + tl = b->cornerRadius.topLeft > 0 ? 0x256d : 0x250c; 322 + tr = b->cornerRadius.topRight > 0 ? 0x256e : 0x2510; 323 + bl = b->cornerRadius.bottomLeft > 0 ? 0x2570 : 0x2514; 324 + br = b->cornerRadius.bottomRight > 0 ? 0x256f : 0x2518; 325 + h_char = 0x2500; v_char = 0x2502; 326 + break; 327 + } 314 328 315 329 if (top && left) 316 330 setcell(ct, x0, y0, tl, fg, bg); ··· 324 338 /* horizontal edges */ 325 339 if (top) 326 340 for (int x = x0 + left; x < x1 - right; x++) 327 - setcell(ct, x, y0, 0x2500, fg, bg); 341 + setcell(ct, x, y0, h_char, fg, bg); 328 342 if (bot) 329 343 for (int x = x0 + left; x < x1 - right; x++) 330 - setcell(ct, x, y1 - 1, 0x2500, fg, bg); 344 + setcell(ct, x, y1 - 1, h_char, fg, bg); 331 345 332 346 /* vertical edges */ 333 347 if (left) 334 348 for (int y = y0 + top; y < y1 - bot; y++) 335 - setcell(ct, x0, y, 0x2502, fg, bg); 349 + setcell(ct, x0, y, v_char, fg, bg); 336 350 if (right) 337 351 for (int y = y0 + top; y < y1 - bot; y++) 338 - setcell(ct, x1 - 1, y, 0x2502, fg, bg); 352 + setcell(ct, x1 - 1, y, v_char, fg, bg); 339 353 } 340 354 341 355 /* ── Command buffer helpers ───────────────────────────────────────── */ ··· 535 549 decl.border.width.right = (bw >> 8) & 0xff; 536 550 decl.border.width.top = (bw >> 16) & 0xff; 537 551 decl.border.width.bottom = (bw >> 24) & 0xff; 552 + decl.border.width.betweenChildren = rd(buf, len, &i) & 0xff; 538 553 } 539 554 540 555 if (mask & PROP_CLIP) {
+70
test/term.test.ts
··· 90 90 ╰──────────────────────────────────────╯`.trim()); 91 91 }); 92 92 93 + it("renders double border style", () => { 94 + let out = print( 95 + decode( 96 + term.render([ 97 + open("box", { 98 + layout: { width: grow(), height: grow() }, 99 + border: { 100 + color: rgba(255, 255, 255), 101 + left: 1, 102 + right: 1, 103 + top: 1, 104 + bottom: 1, 105 + style: "double", 106 + }, 107 + }), 108 + close(), 109 + ]).output, 110 + ), 111 + 40, 112 + 10, 113 + ); 114 + 115 + expect(out).toEqual(` 116 + ╔══════════════════════════════════════╗ 117 + ║ ║ 118 + ║ ║ 119 + ║ ║ 120 + ║ ║ 121 + ║ ║ 122 + ║ ║ 123 + ║ ║ 124 + ║ ║ 125 + ╚══════════════════════════════════════╝`.trim()); 126 + }); 127 + 128 + it("renders bold border style", () => { 129 + let out = print( 130 + decode( 131 + term.render([ 132 + open("box", { 133 + layout: { width: grow(), height: grow() }, 134 + border: { 135 + color: rgba(255, 255, 255), 136 + left: 1, 137 + right: 1, 138 + top: 1, 139 + bottom: 1, 140 + style: "bold", 141 + }, 142 + }), 143 + close(), 144 + ]).output, 145 + ), 146 + 40, 147 + 10, 148 + ); 149 + 150 + expect(out).toEqual(` 151 + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 152 + ┃ ┃ 153 + ┃ ┃ 154 + ┃ ┃ 155 + ┃ ┃ 156 + ┃ ┃ 157 + ┃ ┃ 158 + ┃ ┃ 159 + ┃ ┃ 160 + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛`.trim()); 161 + }); 162 + 93 163 describe("line mode", () => { 94 164 let box = (msg: string) => [ 95 165 open("root", {
+7
validate.ts
··· 73 73 right: Type.Optional(u8), 74 74 top: Type.Optional(u8), 75 75 bottom: Type.Optional(u8), 76 + style: Type.Optional( 77 + Type.Union([ 78 + Type.Literal("single"), 79 + Type.Literal("double"), 80 + Type.Literal("bold"), 81 + ]), 82 + ), 76 83 }); 77 84 78 85 const Clip = Type.Object({