mirror of
https://github.com/anomalyco/opentui.git
synced 2026-09-19 01:26:03 +08:00
feat: add support for transparent terminal background rendering (#225)
This addresses https://github.com/sst/opentui/issues/184 by defaulting to terminal background if renderer alpha channel is 0. The transparent background can be set by using `renderer.setBackgroundColor("transparent")` for example in `packages/react/examples/basic.tsx`. ```diff import { bold, fg, italic, t, TextAttributes } from "@opentui/core" import { render, useKeyboard, useRenderer } from "@opentui/react" import { useCallback, useState } from "react" export const App = () => { const renderer = useRenderer() + renderer.setBackgroundColor("transparent") const [username, setUsername] = useState("") const [password, setPassword] = useState("") ``` Example in ghostty <img width="852" height="670" alt="image" src="https://github.com/user-attachments/assets/658703ad-77a6-4624-b07c-5a24bbf8447c" /> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,7 @@ export interface CliRendererConfig {
|
||||
useConsole?: boolean
|
||||
experimental_splitHeight?: number
|
||||
useKittyKeyboard?: boolean
|
||||
backgroundColor?: ColorInput
|
||||
}
|
||||
|
||||
export type PixelResolution = {
|
||||
@@ -230,7 +231,7 @@ export class CliRenderer extends EventEmitter implements RenderContext {
|
||||
private frameTimes: number[] = []
|
||||
private maxStatSamples: number = 300
|
||||
private postProcessFns: ((buffer: OptimizedBuffer, deltaTime: number) => void)[] = []
|
||||
private backgroundColor: RGBA = RGBA.fromHex("#000000")
|
||||
private backgroundColor: RGBA = RGBA.fromInts(0, 0, 0, 0)
|
||||
private waitingForPixelResolution: boolean = false
|
||||
|
||||
private rendering: boolean = false
|
||||
@@ -1030,7 +1031,7 @@ export class CliRenderer extends EventEmitter implements RenderContext {
|
||||
this.renderOffset = height - this._splitHeight
|
||||
this.width = width
|
||||
this.height = this._splitHeight
|
||||
this.currentRenderBuffer.clear(RGBA.fromHex("#000000"))
|
||||
this.currentRenderBuffer.clear(this.backgroundColor)
|
||||
this.lib.setRenderOffset(this.rendererPtr, this.renderOffset)
|
||||
} else {
|
||||
this.width = width
|
||||
|
||||
@@ -178,7 +178,7 @@ pub const CliRenderer = struct {
|
||||
.currentRenderBuffer = currentBuffer,
|
||||
.nextRenderBuffer = nextBuffer,
|
||||
.pool = pool,
|
||||
.backgroundColor = .{ 0.0, 0.0, 0.0, 1.0 },
|
||||
.backgroundColor = .{ 0.0, 0.0, 0.0, 0.0 },
|
||||
.renderOffset = 0,
|
||||
.terminal = Terminal.init(.{}),
|
||||
.testing = testing,
|
||||
@@ -216,8 +216,8 @@ pub const CliRenderer = struct {
|
||||
.hitGridHeight = height,
|
||||
};
|
||||
|
||||
try currentBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], 1.0 }, CLEAR_CHAR);
|
||||
try nextBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], 1.0 }, null);
|
||||
try currentBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3] }, CLEAR_CHAR);
|
||||
try nextBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3] }, null);
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -386,7 +386,7 @@ pub const CliRenderer = struct {
|
||||
try self.nextRenderBuffer.resize(width, height);
|
||||
|
||||
try self.currentRenderBuffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, CLEAR_CHAR);
|
||||
try self.nextRenderBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], 1.0 }, null);
|
||||
try self.nextRenderBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3] }, null);
|
||||
|
||||
const newHitGridSize = width * height;
|
||||
const currentHitGridSize = self.hitGridWidth * self.hitGridHeight;
|
||||
@@ -589,9 +589,16 @@ pub const CliRenderer = struct {
|
||||
const bgR = rgbaComponentToU8(cell.bg[0]);
|
||||
const bgG = rgbaComponentToU8(cell.bg[1]);
|
||||
const bgB = rgbaComponentToU8(cell.bg[2]);
|
||||
const bgA = cell.bg[3];
|
||||
|
||||
ansi.ANSI.fgColorOutput(writer, fgR, fgG, fgB) catch {};
|
||||
ansi.ANSI.bgColorOutput(writer, bgR, bgG, bgB) catch {};
|
||||
|
||||
// If alpha is 0 (transparent), use terminal default background instead of black
|
||||
if (bgA < 0.001) {
|
||||
writer.writeAll("\x1b[49m") catch {};
|
||||
} else {
|
||||
ansi.ANSI.bgColorOutput(writer, bgR, bgG, bgB) catch {};
|
||||
}
|
||||
|
||||
ansi.TextAttributes.applyAttributesOutputWriter(writer, cell.attributes) catch {};
|
||||
}
|
||||
@@ -686,7 +693,7 @@ pub const CliRenderer = struct {
|
||||
self.renderStats.cellsUpdated = cellsUpdated;
|
||||
self.renderStats.renderTime = renderTime;
|
||||
|
||||
self.nextRenderBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], 1.0 }, null) catch {};
|
||||
self.nextRenderBuffer.clear(.{ self.backgroundColor[0], self.backgroundColor[1], self.backgroundColor[2], self.backgroundColor[3] }, null) catch {};
|
||||
|
||||
const temp = self.currentHitGrid;
|
||||
self.currentHitGrid = self.nextHitGrid;
|
||||
|
||||
@@ -128,6 +128,45 @@ if [ "$LINK_REACT" = true ]; then
|
||||
else
|
||||
echo "Warning: $REACT_PATH not found"
|
||||
fi
|
||||
|
||||
# Only link react, react-dom, and react-reconciler when not in copy mode
|
||||
if [ "$COPY_MODE" = false ]; then
|
||||
# Link react
|
||||
remove_if_exists "$NODE_MODULES_DIR/react"
|
||||
if [ -d "$OPENTUI_ROOT/node_modules/react" ]; then
|
||||
ln -s "$OPENTUI_ROOT/node_modules/react" "$NODE_MODULES_DIR/react"
|
||||
echo "✓ Linked react"
|
||||
elif [ -d "$OPENTUI_ROOT/packages/react/node_modules/react" ]; then
|
||||
ln -s "$OPENTUI_ROOT/packages/react/node_modules/react" "$NODE_MODULES_DIR/react"
|
||||
echo "✓ Linked react (from packages/react/node_modules)"
|
||||
else
|
||||
echo "Warning: react not found in OpenTUI node_modules"
|
||||
fi
|
||||
|
||||
# Link react-dom
|
||||
remove_if_exists "$NODE_MODULES_DIR/react-dom"
|
||||
if [ -d "$OPENTUI_ROOT/node_modules/react-dom" ]; then
|
||||
ln -s "$OPENTUI_ROOT/node_modules/react-dom" "$NODE_MODULES_DIR/react-dom"
|
||||
echo "✓ Linked react-dom"
|
||||
elif [ -d "$OPENTUI_ROOT/packages/react/node_modules/react-dom" ]; then
|
||||
ln -s "$OPENTUI_ROOT/packages/react/node_modules/react-dom" "$NODE_MODULES_DIR/react-dom"
|
||||
echo "✓ Linked react-dom (from packages/react/node_modules)"
|
||||
else
|
||||
echo "Warning: react-dom not found in OpenTUI node_modules"
|
||||
fi
|
||||
|
||||
# Link react-reconciler
|
||||
remove_if_exists "$NODE_MODULES_DIR/react-reconciler"
|
||||
if [ -d "$OPENTUI_ROOT/node_modules/react-reconciler" ]; then
|
||||
ln -s "$OPENTUI_ROOT/node_modules/react-reconciler" "$NODE_MODULES_DIR/react-reconciler"
|
||||
echo "✓ Linked react-reconciler"
|
||||
elif [ -d "$OPENTUI_ROOT/packages/react/node_modules/react-reconciler" ]; then
|
||||
ln -s "$OPENTUI_ROOT/packages/react/node_modules/react-reconciler" "$NODE_MODULES_DIR/react-reconciler"
|
||||
echo "✓ Linked react-reconciler (from packages/react/node_modules)"
|
||||
else
|
||||
echo "Warning: react-reconciler not found in OpenTUI node_modules"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Link Solid and solid-js if requested
|
||||
|
||||
Reference in New Issue
Block a user