Skip to content

Commit 0a347a8

Browse files
committed
Add transparency for unfocused panes
1 parent 53dcd49 commit 0a347a8

File tree

4 files changed

+207
-7
lines changed

4 files changed

+207
-7
lines changed

src/Surface.zig

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -590,10 +590,8 @@ const fallback_palette: [16]vaxis.Cell.Color = .{
590590
.{ .rgb = .{ 0xff, 0xff, 0xff } }, // Bright White
591591
};
592592

593-
const DIM_UNFOCUSED: f32 = 0.05;
594-
595-
pub fn render(self: *const Surface, win: vaxis.Window, focused: bool) void {
596-
const dim_factor: f32 = if (focused) 0.0 else DIM_UNFOCUSED;
593+
pub fn render(self: *const Surface, win: vaxis.Window, focused: bool, colors: ?*const TerminalColors, dim_factor: f32) void {
594+
_ = colors; // Colors are stored in self.colors
597595

598596
for (0..self.rows) |row| {
599597
for (0..self.cols) |col| {
@@ -620,6 +618,9 @@ pub fn render(self: *const Surface, win: vaxis.Window, focused: bool) void {
620618
}
621619

622620
fn applyDimming(self: *const Surface, cell: *vaxis.Cell, dim_factor: f32) bool {
621+
// Skip dimming for default backgrounds to preserve transparency
622+
if (cell.style.bg == .default) return false;
623+
623624
const bg_rgb = self.resolveBgColor(cell.style.bg) orelse return false;
624625
const dimmed_bg = TerminalColors.reduceContrast(bg_rgb, dim_factor);
625626
cell.style.bg = .{ .rgb = dimmed_bg };
@@ -630,7 +631,7 @@ fn resolveBgColor(self: *const Surface, bg: vaxis.Cell.Color) ?[3]u8 {
630631
return switch (bg) {
631632
.rgb => |rgb| rgb,
632633
.index => |idx| self.resolvePaletteColor(idx),
633-
.default => self.resolveDefaultBg(),
634+
.default => null, // Don't resolve default - preserve transparency
634635
};
635636
}
636637

src/client.zig

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,8 +1537,148 @@ pub const App = struct {
15371537
}
15381538
}
15391539

1540+
fn applyDimToStyle(self: *App, style: vaxis.Style, dim_factor: f32) vaxis.Style {
1541+
if (dim_factor == 0.0) return style;
1542+
1543+
var dimmed = style;
1544+
1545+
// Apply dimming to foreground color
1546+
if (dimmed.fg != .default) {
1547+
const fg_rgb = self.resolveColor(dimmed.fg);
1548+
if (fg_rgb) |rgb| {
1549+
dimmed.fg = .{ .rgb = Surface.TerminalColors.reduceContrast(rgb, dim_factor * 0.5) };
1550+
}
1551+
}
1552+
1553+
// Apply dimming to background color
1554+
if (dimmed.bg != .default) {
1555+
const bg_rgb = self.resolveColor(dimmed.bg);
1556+
if (bg_rgb) |rgb| {
1557+
dimmed.bg = .{ .rgb = Surface.TerminalColors.reduceContrast(rgb, dim_factor) };
1558+
}
1559+
}
1560+
1561+
return dimmed;
1562+
}
1563+
1564+
fn resolveColor(self: *App, color: vaxis.Cell.Color) ?[3]u8 {
1565+
return switch (color) {
1566+
.rgb => |rgb| rgb,
1567+
.index => |idx| blk: {
1568+
if (idx >= 16) break :blk null;
1569+
if (self.colors.palette[idx]) |c_val| {
1570+
break :blk switch (c_val) {
1571+
.rgb => |rgb| rgb,
1572+
else => null,
1573+
};
1574+
}
1575+
break :blk null;
1576+
},
1577+
.default => blk: {
1578+
if (self.colors.bg) |bg| {
1579+
break :blk switch (bg) {
1580+
.rgb => |rgb| rgb,
1581+
else => null,
1582+
};
1583+
}
1584+
break :blk null;
1585+
},
1586+
};
1587+
}
1588+
15401589
fn renderWidget(self: *App, w: widget.Widget, win: vaxis.Window) !void {
1541-
try w.renderTo(win, self.allocator);
1590+
switch (w.kind) {
1591+
.surface => |surf| {
1592+
if (self.surfaces.get(surf.pty_id)) |surface| {
1593+
// Check for resize mismatch and send resize request if needed
1594+
if (surface.rows != w.height or surface.cols != w.width) {
1595+
log.debug("renderWidget: surface resize needed for pty {}: {}x{} -> {}x{}", .{
1596+
surf.pty_id,
1597+
surface.cols,
1598+
surface.rows,
1599+
w.width,
1600+
w.height,
1601+
});
1602+
self.sendResize(surf.pty_id, w.height, w.width) catch |err| {
1603+
log.err("Failed to send resize: {}", .{err});
1604+
};
1605+
}
1606+
// Calculate dim factor based on focus
1607+
const dim_factor: f32 = if (w.focus) 0.0 else self.ui.dim_factor;
1608+
surface.render(win, w.focus, &self.colors, dim_factor);
1609+
}
1610+
},
1611+
.text => {
1612+
// Use renderTo for text widgets
1613+
try w.renderTo(win, self.allocator);
1614+
},
1615+
.box => |b| {
1616+
const chars = b.borderChars();
1617+
// Apply dimming to border style if not focused
1618+
const dim_factor: f32 = if (w.focus) 0.0 else self.ui.dim_factor;
1619+
const style = self.applyDimToStyle(b.style, dim_factor);
1620+
1621+
log.debug("render box: w={} h={} focus={} dim_factor={d}", .{ win.width, win.height, w.focus, dim_factor });
1622+
1623+
// Fill background for the entire box area
1624+
if (style.bg != .default) {
1625+
for (0..win.height) |row| {
1626+
for (0..win.width) |col| {
1627+
win.writeCell(@intCast(col), @intCast(row), .{
1628+
.char = .{ .grapheme = " ", .width = 1 },
1629+
.style = style,
1630+
});
1631+
}
1632+
}
1633+
}
1634+
1635+
// Render border if not none
1636+
if (b.border != .none and win.width >= 2 and win.height >= 2) {
1637+
win.writeCell(0, 0, .{ .char = .{ .grapheme = chars.tl, .width = 1 }, .style = style });
1638+
win.writeCell(win.width - 1, 0, .{ .char = .{ .grapheme = chars.tr, .width = 1 }, .style = style });
1639+
win.writeCell(0, win.height - 1, .{ .char = .{ .grapheme = chars.bl, .width = 1 }, .style = style });
1640+
win.writeCell(win.width - 1, win.height - 1, .{ .char = .{ .grapheme = chars.br, .width = 1 }, .style = style });
1641+
1642+
for (1..win.width - 1) |col| {
1643+
win.writeCell(@intCast(col), 0, .{ .char = .{ .grapheme = chars.h, .width = 1 }, .style = style });
1644+
win.writeCell(@intCast(col), win.height - 1, .{ .char = .{ .grapheme = chars.h, .width = 1 }, .style = style });
1645+
}
1646+
1647+
for (1..win.height - 1) |row| {
1648+
win.writeCell(0, @intCast(row), .{ .char = .{ .grapheme = chars.v, .width = 1 }, .style = style });
1649+
win.writeCell(win.width - 1, @intCast(row), .{ .char = .{ .grapheme = chars.v, .width = 1 }, .style = style });
1650+
}
1651+
}
1652+
1653+
// Render child widget if present
1654+
const inner_win = win.child(.{
1655+
.x_off = if (b.border != .none) 1 else 0,
1656+
.y_off = if (b.border != .none) 1 else 0,
1657+
.width = if (b.border != .none and win.width >= 2) win.width - 2 else win.width,
1658+
.height = if (b.border != .none and win.height >= 2) win.height - 2 else win.height,
1659+
});
1660+
try self.renderWidget(b.child.*, inner_win);
1661+
},
1662+
.positioned => |pos| {
1663+
const child_win = win.child(.{
1664+
.x_off = pos.x orelse 0,
1665+
.y_off = pos.y orelse 0,
1666+
.width = pos.child.width,
1667+
.height = pos.child.height,
1668+
});
1669+
try self.renderWidget(pos.child.*, child_win);
1670+
},
1671+
.text_input,
1672+
.list,
1673+
.padding,
1674+
.column,
1675+
.row,
1676+
.stack,
1677+
=> {
1678+
// These widgets use renderTo for now
1679+
try w.renderTo(win, self.allocator);
1680+
},
1681+
}
15421682
}
15431683

15441684
pub fn scheduleRender(self: *App) !void {

src/ui.zig

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub const UI = struct {
8787
get_session_name_ctx: *anyopaque = undefined,
8888
rename_session_callback: ?*const fn (ctx: *anyopaque, new_name: []const u8) anyerror!void = null,
8989
rename_session_ctx: *anyopaque = undefined,
90+
dim_factor: f32 = 0.025,
9091
text_inputs: std.AutoHashMap(u32, *TextInput),
9192
next_text_input_id: u32 = 1,
9293

@@ -215,6 +216,19 @@ pub const UI = struct {
215216
// Store pointer to self in registry for static functions to use
216217
self.lua.pushLightUserdata(self);
217218
self.lua.setField(ziglua.registry_index, "prise_ui_ptr");
219+
220+
// Apply any pending dim_factor that was set during init.lua
221+
_ = self.lua.getField(ziglua.registry_index, "_pending_dim_factor");
222+
if (self.lua.typeOf(-1) == .number) {
223+
const pending_dim = self.lua.toNumber(-1) catch 0.025;
224+
log.debug("setLoop: applying pending dim_factor={d}", .{pending_dim});
225+
self.dim_factor = @floatCast(pending_dim);
226+
self.lua.pop(1);
227+
self.lua.pushNil();
228+
self.lua.setField(ziglua.registry_index, "_pending_dim_factor");
229+
} else {
230+
self.lua.pop(1);
231+
}
218232
}
219233

220234
pub fn setExitCallback(self: *UI, ctx: *anyopaque, cb: *const fn (ctx: *anyopaque) void) void {
@@ -375,6 +389,14 @@ pub const UI = struct {
375389
lua.pushFunction(ziglua.wrap(renameSession));
376390
lua.setField(-2, "rename_session");
377391

392+
// Register set_dim_factor
393+
lua.pushFunction(ziglua.wrap(setDimFactor));
394+
lua.setField(-2, "set_dim_factor");
395+
396+
// Register get_dim_factor
397+
lua.pushFunction(ziglua.wrap(getDimFactor));
398+
lua.setField(-2, "get_dim_factor");
399+
378400
// Register create_text_input
379401
lua.pushFunction(ziglua.wrap(createTextInput));
380402
lua.setField(-2, "create_text_input");
@@ -639,6 +661,43 @@ pub const UI = struct {
639661
return 1;
640662
}
641663

664+
fn getDimFactor(lua: *ziglua.Lua) i32 {
665+
_ = lua.getField(ziglua.registry_index, "prise_ui_ptr");
666+
const ui_ptr = lua.toPointer(-1) catch {
667+
lua.pushNumber(0.025);
668+
return 1;
669+
};
670+
lua.pop(1);
671+
672+
const ui: *UI = @ptrCast(@alignCast(@constCast(ui_ptr)));
673+
lua.pushNumber(ui.dim_factor);
674+
return 1;
675+
}
676+
677+
fn setDimFactor(lua: *ziglua.Lua) i32 {
678+
const dim: f64 = lua.toNumber(1) catch 0.025;
679+
680+
// Try to get the UI pointer and set it directly
681+
_ = lua.getField(ziglua.registry_index, "prise_ui_ptr");
682+
const ui_ptr_result = lua.toPointer(-1);
683+
lua.pop(1);
684+
685+
if (ui_ptr_result) |ui_ptr| {
686+
const ui: *UI = @ptrCast(@alignCast(@constCast(ui_ptr)));
687+
log.debug("setDimFactor: setting dim_factor to {d}", .{dim});
688+
ui.dim_factor = @floatCast(dim);
689+
lua.pushBoolean(true);
690+
return 1;
691+
} else |_| {
692+
// UI pointer not available yet (we're in init.lua), store the value for later
693+
log.debug("setDimFactor: storing pending dim_factor={d}", .{dim});
694+
lua.pushNumber(dim);
695+
lua.setField(ziglua.registry_index, "_pending_dim_factor");
696+
lua.pushBoolean(true);
697+
return 1;
698+
}
699+
}
700+
642701
fn detach(lua: *ziglua.Lua) i32 {
643702
_ = lua.getField(ziglua.registry_index, "prise_ui_ptr");
644703
const ui = lua.toUserdata(UI, -1) catch {

src/widget.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ pub const Widget = struct {
252252
pub fn renderTo(self: *const Widget, win: vaxis.Window, allocator: std.mem.Allocator) !void {
253253
switch (self.kind) {
254254
.surface => |surf| {
255-
surf.surface.render(win, self.focus);
255+
surf.surface.render(win, self.focus, null, 0.0);
256256
},
257257
.text_input => |ti| {
258258
ti.input.updateScrollOffset(@intCast(win.width));

0 commit comments

Comments
 (0)