Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,8 @@ const fallback_palette: [16]vaxis.Cell.Color = .{
.{ .rgb = .{ 0xff, 0xff, 0xff } }, // Bright White
};

const DIM_UNFOCUSED: f32 = 0.05;

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

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

fn applyDimming(self: *const Surface, cell: *vaxis.Cell, dim_factor: f32) bool {
// Skip dimming for default backgrounds to preserve transparency
if (cell.style.bg == .default) return false;

const bg_rgb = self.resolveBgColor(cell.style.bg) orelse return false;
const dimmed_bg = TerminalColors.reduceContrast(bg_rgb, dim_factor);
cell.style.bg = .{ .rgb = dimmed_bg };
Expand All @@ -630,7 +631,7 @@ fn resolveBgColor(self: *const Surface, bg: vaxis.Cell.Color) ?[3]u8 {
return switch (bg) {
.rgb => |rgb| rgb,
.index => |idx| self.resolvePaletteColor(idx),
.default => self.resolveDefaultBg(),
.default => null, // Don't resolve default - preserve transparency
};
}

Expand Down
143 changes: 142 additions & 1 deletion src/client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1537,8 +1537,149 @@ pub const App = struct {
}
}

fn applyDimToStyle(self: *App, style: vaxis.Style, dim_factor: f32) vaxis.Style {
if (dim_factor == 0.0) return style;

var dimmed = style;

// Apply dimming to foreground color
if (dimmed.fg != .default) {
const fg_rgb = self.resolveColor(dimmed.fg);
if (fg_rgb) |rgb| {
dimmed.fg = .{ .rgb = Surface.TerminalColors.reduceContrast(rgb, dim_factor * 0.5) };
}
}

// Apply dimming to background color
if (dimmed.bg != .default) {
const bg_rgb = self.resolveColor(dimmed.bg);
if (bg_rgb) |rgb| {
dimmed.bg = .{ .rgb = Surface.TerminalColors.reduceContrast(rgb, dim_factor) };
}
}

return dimmed;
}

fn resolveColor(self: *App, color: vaxis.Cell.Color) ?[3]u8 {
return switch (color) {
.rgb => |rgb| rgb,
.index => |idx| blk: {
if (idx >= 16) break :blk null;
if (self.colors.palette[idx]) |c_val| {
break :blk switch (c_val) {
.rgb => |rgb| rgb,
else => null,
};
}
break :blk null;
},
.default => blk: {
if (self.colors.bg) |bg| {
break :blk switch (bg) {
.rgb => |rgb| rgb,
else => null,
};
}
break :blk null;
},
};
}

fn renderWidget(self: *App, w: widget.Widget, win: vaxis.Window) !void {
try w.renderTo(win, self.allocator);
switch (w.kind) {
.surface => |surf| {
if (self.surfaces.get(surf.pty_id)) |surface| {
// Check for resize mismatch and send resize request if needed
if (surface.rows != w.height or surface.cols != w.width) {
log.debug("renderWidget: surface resize needed for pty {}: {}x{} -> {}x{}", .{
surf.pty_id,
surface.cols,
surface.rows,
w.width,
w.height,
});
self.sendResize(surf.pty_id, w.height, w.width) catch |err| {
log.err("Failed to send resize: {}", .{err});
};
}
// Calculate dim factor based on focus
const dim_factor: f32 = if (w.focus) 0.0 else self.ui.dim_factor;
surface.render(win, w.focus, &self.colors, dim_factor);
}
},
.text => {
// Use renderTo for text widgets
try w.renderTo(win, self.allocator);
},
.box => |b| {
const chars = b.borderChars();
// Apply dimming to border style if not focused
const dim_factor: f32 = if (w.focus) 0.0 else self.ui.dim_factor;
const style = self.applyDimToStyle(b.style, dim_factor);

log.debug("render box: w={} h={} focus={} dim_factor={d}", .{ win.width, win.height, w.focus, dim_factor });

// Fill background for the entire box area
if (style.bg != .default) {
for (0..win.height) |row| {
for (0..win.width) |col| {
win.writeCell(@intCast(col), @intCast(row), .{
.char = .{ .grapheme = " ", .width = 1 },
.style = style,
});
}
}
}

// Render border if not none
if (b.border != .none and win.width >= 2 and win.height >= 2) {
win.writeCell(0, 0, .{ .char = .{ .grapheme = chars.tl, .width = 1 }, .style = style });
win.writeCell(win.width - 1, 0, .{ .char = .{ .grapheme = chars.tr, .width = 1 }, .style = style });
win.writeCell(0, win.height - 1, .{ .char = .{ .grapheme = chars.bl, .width = 1 }, .style = style });
win.writeCell(win.width - 1, win.height - 1, .{ .char = .{ .grapheme = chars.br, .width = 1 }, .style = style });

for (1..win.width - 1) |col| {
win.writeCell(@intCast(col), 0, .{ .char = .{ .grapheme = chars.h, .width = 1 }, .style = style });
win.writeCell(@intCast(col), win.height - 1, .{ .char = .{ .grapheme = chars.h, .width = 1 }, .style = style });
}

for (1..win.height - 1) |row| {
win.writeCell(0, @intCast(row), .{ .char = .{ .grapheme = chars.v, .width = 1 }, .style = style });
win.writeCell(win.width - 1, @intCast(row), .{ .char = .{ .grapheme = chars.v, .width = 1 }, .style = style });
}
}

// Render child widget if present
const inner_win = win.child(.{
.x_off = if (b.border != .none) 1 else 0,
.y_off = if (b.border != .none) 1 else 0,
.width = if (b.border != .none and win.width >= 2) win.width - 2 else win.width,
.height = if (b.border != .none and win.height >= 2) win.height - 2 else win.height,
});
try self.renderWidget(b.child.*, inner_win);
},
.positioned => |pos| {
const child_win = win.child(.{
.x_off = pos.x orelse 0,
.y_off = pos.y orelse 0,
.width = pos.child.width,
.height = pos.child.height,
});
try self.renderWidget(pos.child.*, child_win);
},
.text_input,
.list,
.padding,
.column,
.row,
.stack,
.separator,
=> {
// These widgets use renderTo for now
try w.renderTo(win, self.allocator);
},
}
}

pub fn scheduleRender(self: *App) !void {
Expand Down
59 changes: 59 additions & 0 deletions src/ui.zig
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ pub const UI = struct {
get_session_name_ctx: *anyopaque = undefined,
rename_session_callback: ?*const fn (ctx: *anyopaque, new_name: []const u8) anyerror!void = null,
rename_session_ctx: *anyopaque = undefined,
dim_factor: f32 = 0.025,
text_inputs: std.AutoHashMap(u32, *TextInput),
next_text_input_id: u32 = 1,

Expand Down Expand Up @@ -215,6 +216,19 @@ pub const UI = struct {
// Store pointer to self in registry for static functions to use
self.lua.pushLightUserdata(self);
self.lua.setField(ziglua.registry_index, "prise_ui_ptr");

// Apply any pending dim_factor that was set during init.lua
_ = self.lua.getField(ziglua.registry_index, "_pending_dim_factor");
if (self.lua.typeOf(-1) == .number) {
const pending_dim = self.lua.toNumber(-1) catch 0.025;
log.debug("setLoop: applying pending dim_factor={d}", .{pending_dim});
self.dim_factor = @floatCast(pending_dim);
self.lua.pop(1);
self.lua.pushNil();
self.lua.setField(ziglua.registry_index, "_pending_dim_factor");
} else {
self.lua.pop(1);
}
}

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

// Register set_dim_factor
lua.pushFunction(ziglua.wrap(setDimFactor));
lua.setField(-2, "set_dim_factor");

// Register get_dim_factor
lua.pushFunction(ziglua.wrap(getDimFactor));
lua.setField(-2, "get_dim_factor");

// Register create_text_input
lua.pushFunction(ziglua.wrap(createTextInput));
lua.setField(-2, "create_text_input");
Expand Down Expand Up @@ -639,6 +661,43 @@ pub const UI = struct {
return 1;
}

fn getDimFactor(lua: *ziglua.Lua) i32 {
_ = lua.getField(ziglua.registry_index, "prise_ui_ptr");
const ui_ptr = lua.toPointer(-1) catch {
lua.pushNumber(0.025);
return 1;
};
lua.pop(1);

const ui: *UI = @ptrCast(@alignCast(@constCast(ui_ptr)));
lua.pushNumber(ui.dim_factor);
return 1;
}

fn setDimFactor(lua: *ziglua.Lua) i32 {
const dim: f64 = lua.toNumber(1) catch 0.025;

// Try to get the UI pointer and set it directly
_ = lua.getField(ziglua.registry_index, "prise_ui_ptr");
const ui_ptr_result = lua.toPointer(-1);
lua.pop(1);

if (ui_ptr_result) |ui_ptr| {
const ui: *UI = @ptrCast(@alignCast(@constCast(ui_ptr)));
log.debug("setDimFactor: setting dim_factor to {d}", .{dim});
ui.dim_factor = @floatCast(dim);
lua.pushBoolean(true);
return 1;
} else |_| {
// UI pointer not available yet (we're in init.lua), store the value for later
log.debug("setDimFactor: storing pending dim_factor={d}", .{dim});
lua.pushNumber(dim);
lua.setField(ziglua.registry_index, "_pending_dim_factor");
lua.pushBoolean(true);
return 1;
}
}

fn detach(lua: *ziglua.Lua) i32 {
_ = lua.getField(ziglua.registry_index, "prise_ui_ptr");
const ui = lua.toUserdata(UI, -1) catch {
Expand Down
2 changes: 1 addition & 1 deletion src/widget.zig
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ pub const Widget = struct {
pub fn renderTo(self: *const Widget, win: vaxis.Window, allocator: std.mem.Allocator) !void {
switch (self.kind) {
.surface => |surf| {
surf.surface.render(win, self.focus);
surf.surface.render(win, self.focus, null, 0.0);
},
.text_input => |ti| {
ti.input.updateScrollOffset(@intCast(win.width));
Expand Down