Compare commits

...

9 commits

17 changed files with 320 additions and 115 deletions

111
build.zig
View file

@ -114,17 +114,21 @@ fn build_release(
all_targets: bool,
test_filters: []const []const u8,
) void {
const targets: []const std.Target.Query = if (all_targets) &.{
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
.{ .cpu_arch = .x86, .os_tag = .linux, .abi = .musl },
.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl },
.{ .cpu_arch = .arm, .os_tag = .linux, .abi = .musleabihf },
.{ .cpu_arch = .x86_64, .os_tag = .macos },
.{ .cpu_arch = .aarch64, .os_tag = .macos },
.{ .cpu_arch = .x86_64, .os_tag = .windows },
.{ .cpu_arch = .aarch64, .os_tag = .windows },
.{ .cpu_arch = .x86_64, .os_tag = .freebsd },
.{ .cpu_arch = .aarch64, .os_tag = .freebsd },
const targets: []const struct { std.Target.Query, Renderer } = if (all_targets) &.{
.{ .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl }, .terminal },
// .{ .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = null }, .gui },
.{ .{ .cpu_arch = .x86, .os_tag = .linux, .abi = .musl }, .terminal },
.{ .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl }, .terminal },
// .{ .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = null }, .gui },
.{ .{ .cpu_arch = .arm, .os_tag = .linux, .abi = .musleabihf }, .terminal },
.{ .{ .cpu_arch = .x86_64, .os_tag = .macos }, .terminal },
.{ .{ .cpu_arch = .aarch64, .os_tag = .macos }, .terminal },
.{ .{ .cpu_arch = .x86_64, .os_tag = .windows }, .terminal },
.{ .{ .cpu_arch = .x86_64, .os_tag = .windows }, .d3d11 },
.{ .{ .cpu_arch = .aarch64, .os_tag = .windows }, .terminal },
.{ .{ .cpu_arch = .aarch64, .os_tag = .windows }, .d3d11 },
.{ .{ .cpu_arch = .x86_64, .os_tag = .freebsd }, .terminal },
.{ .{ .cpu_arch = .aarch64, .os_tag = .freebsd }, .terminal },
} else blk: {
const maybe_triple = b.option(
[]const u8,
@ -133,8 +137,18 @@ fn build_release(
);
const triple = maybe_triple orelse {
const native_target = b.resolveTargetQuery(.{}).result;
break :blk &.{
.{ .cpu_arch = native_target.cpu.arch, .os_tag = native_target.os.tag },
break :blk switch (native_target.os.tag) {
.linux => &.{
.{ .{ .cpu_arch = native_target.cpu.arch, .os_tag = native_target.os.tag, .abi = .musl }, .terminal },
// .{ .{ .cpu_arch = native_target.cpu.arch, .os_tag = native_target.os.tag, .abi = null }, .gui },
},
.windows => &.{
.{ .{ .cpu_arch = native_target.cpu.arch, .os_tag = native_target.os.tag }, .terminal },
.{ .{ .cpu_arch = native_target.cpu.arch, .os_tag = native_target.os.tag }, .d3d11 },
},
else => &.{
.{ .{ .cpu_arch = native_target.cpu.arch, .os_tag = native_target.os.tag }, .terminal },
},
};
};
const selected_target = std.Build.parseTargetQuery(.{
@ -142,8 +156,18 @@ fn build_release(
}) catch |err| switch (err) {
error.ParseFailed => @panic("unknown target"),
};
break :blk &.{
.{ .cpu_arch = selected_target.cpu_arch, .os_tag = selected_target.os_tag, .abi = selected_target.abi },
break :blk switch (selected_target.os_tag.?) {
.linux => &.{
.{ .{ .cpu_arch = selected_target.cpu_arch, .os_tag = selected_target.os_tag, .abi = .musl }, .terminal },
// .{ .{ .cpu_arch = selected_target.cpu_arch, .os_tag = selected_target.os_tag, .abi = .gnu }, .gui },
},
.windows => &.{
.{ .{ .cpu_arch = selected_target.cpu_arch, .os_tag = selected_target.os_tag, .abi = selected_target.abi }, .terminal },
.{ .{ .cpu_arch = selected_target.cpu_arch, .os_tag = selected_target.os_tag, .abi = selected_target.abi }, .d3d11 },
},
else => &.{
.{ .{ .cpu_arch = selected_target.cpu_arch, .os_tag = selected_target.os_tag, .abi = selected_target.abi }, .terminal },
},
};
};
const optimize = b.standardOptimizeOption(.{});
@ -155,8 +179,9 @@ fn build_release(
b.getInstallStep().dependOn(&b.addInstallFile(version_file, "version").step);
for (targets) |t| {
const target = b.resolveTargetQuery(t);
var triple = std.mem.splitScalar(u8, t.zigTriple(b.allocator) catch unreachable, '-');
const renderer = t.@"1";
const target = b.resolveTargetQuery(t.@"0");
var triple = std.mem.splitScalar(u8, t.@"0".zigTriple(b.allocator) catch unreachable, '-');
const arch = triple.next() orelse unreachable;
const os = triple.next() orelse unreachable;
const target_path = std.mem.join(b.allocator, "-", &[_][]const u8{ os, arch }) catch unreachable;
@ -176,7 +201,7 @@ fn build_release(
true, // strip release builds
use_llvm,
pie,
.terminal,
renderer,
version,
test_filters,
);
@ -195,50 +220,10 @@ fn build_release(
false, // don't strip debug builds
use_llvm,
pie,
.terminal,
renderer,
version,
test_filters,
);
if (t.os_tag == .windows) {
build_exe(
b,
run_step,
check_step,
test_step,
lint_step,
target,
optimize_release,
.{ .dest_dir = .{ .override = .{ .custom = target_path } } },
tracy_enabled,
use_tree_sitter,
true, // strip release builds
use_llvm,
pie,
.d3d11,
version,
test_filters,
);
build_exe(
b,
run_step,
check_step,
test_step,
lint_step,
target,
optimize_debug,
.{ .dest_dir = .{ .override = .{ .custom = target_path_debug } } },
tracy_enabled,
use_tree_sitter,
false, // don't strip debug builds
use_llvm,
pie,
.d3d11,
version,
test_filters,
);
}
}
}
@ -506,6 +491,10 @@ pub fn build_exe(
.d3d11 => {
const win32_dep = b.lazyDependency("win32", .{}) orelse break :blk tui_renderer_mod;
const win32_mod = win32_dep.module("win32");
const gui_xy_mod = b.createModule(.{ .root_source_file = b.path("src/gui/xy.zig") });
const gui_cell_mod = b.createModule(.{ .root_source_file = b.path("src/gui/Cell.zig") });
const gui_glyph_cache_mod = b.createModule(.{ .root_source_file = b.path("src/gui/GlyphIndexCache.zig") });
const gui_xterm_mod = b.createModule(.{ .root_source_file = b.path("src/gui/xterm.zig") });
const gui_mod = b.createModule(.{
.root_source_file = b.path("src/win32/gui.zig"),
.imports = &.{
@ -519,6 +508,10 @@ pub fn build_exe(
.{ .name = "color", .module = color_mod },
.{ .name = "gui_config", .module = gui_config_mod },
.{ .name = "tracy", .module = tracy_mod },
.{ .name = "xy", .module = gui_xy_mod },
.{ .name = "Cell", .module = gui_cell_mod },
.{ .name = "GlyphIndexCache", .module = gui_glyph_cache_mod },
.{ .name = "xterm", .module = gui_xterm_mod },
},
});
gui_mod.addIncludePath(b.path("src/win32"));

View file

@ -15,6 +15,14 @@ pub const FsParams = extern struct {
col_count: i32,
row_count: i32,
viewport_height: i32,
// Primary cursor (position + appearance)
cursor_col: i32,
cursor_row: i32,
cursor_shape: i32, // 0=block, 1=beam, 2=underline
cursor_vis: i32, // 0=hidden, 1=visible
cursor_color: [4]f32, // RGBA normalized [0,1]
// Secondary cursor colour (positions encoded in ShaderCell._pad)
sec_cursor_color: [4]f32, // RGBA normalized [0,1]
};
const vs_src =
@ -34,6 +42,12 @@ const fs_src =
\\uniform int col_count;
\\uniform int row_count;
\\uniform int viewport_height;
\\uniform int cursor_col;
\\uniform int cursor_row;
\\uniform int cursor_shape;
\\uniform int cursor_vis;
\\uniform vec4 cursor_color;
\\uniform vec4 sec_cursor_color;
\\uniform sampler2D glyph_tex_glyph_smp;
\\uniform usampler2D cell_tex_cell_smp;
\\out vec4 frag_color;
@ -59,7 +73,7 @@ const fs_src =
\\ return;
\\ }
\\
\\ // Fetch cell: texel = (glyph_index, bg_packed, fg_packed, 0)
\\ // Fetch cell: texel = (glyph_index, bg_packed, fg_packed, cursor_flag)
\\ uvec4 cell = texelFetch(cell_tex_cell_smp, ivec2(col, row), 0);
\\ vec4 bg = unpack_rgba(cell.g);
\\ vec4 fg = unpack_rgba(cell.b);
@ -78,9 +92,31 @@ const fs_src =
\\ gr * cell_size_y + cell_px_y);
\\ float glyph_alpha = texelFetch(glyph_tex_glyph_smp, atlas_coord, 0).r;
\\
\\ // Blend fg over bg
\\ vec3 color = mix(bg.rgb, fg.rgb, fg.a * glyph_alpha);
\\ frag_color = vec4(color, 1.0);
\\ // Cursor detection
\\ bool is_primary = (cursor_vis != 0) && (col == cursor_col) && (row == cursor_row);
\\ bool is_secondary = (cell.a != 0u);
\\
\\ vec3 final_bg = bg.rgb;
\\ vec3 final_fg = fg.rgb;
\\
\\ if (is_primary || is_secondary) {
\\ vec4 cur = is_primary ? cursor_color : sec_cursor_color;
\\ int shape = cursor_shape;
\\
\\ if (shape == 1) {
\\ // Beam: 2px vertical bar at left edge of cell
\\ if (cell_px_x < 2) { frag_color = vec4(cur.rgb, 1.0); return; }
\\ } else if (shape == 2) {
\\ // Underline: 2px horizontal bar at bottom of cell
\\ if (cell_px_y >= cell_size_y - 2) { frag_color = vec4(cur.rgb, 1.0); return; }
\\ } else {
\\ // Block: cursor colour as bg, inverted for glyph contrast
\\ final_bg = cur.rgb;
\\ final_fg = vec3(1.0) - cur.rgb;
\\ }
\\ }
\\
\\ frag_color = vec4(mix(final_bg, final_fg, fg.a * glyph_alpha), 1.0);
\\}
;
@ -91,7 +127,7 @@ pub fn shaderDesc(backend: sg.Backend) sg.ShaderDesc {
desc.vertex_func.source = vs_src;
desc.fragment_func.source = fs_src;
// Fragment uniform block: 4 individual INT uniforms
// Fragment uniform block: individual uniforms (GLCORE uses glUniform* calls)
desc.uniform_blocks[0].stage = .FRAGMENT;
desc.uniform_blocks[0].size = @sizeOf(FsParams);
desc.uniform_blocks[0].layout = .NATIVE;
@ -100,6 +136,12 @@ pub fn shaderDesc(backend: sg.Backend) sg.ShaderDesc {
desc.uniform_blocks[0].glsl_uniforms[2] = .{ .type = .INT, .glsl_name = "col_count" };
desc.uniform_blocks[0].glsl_uniforms[3] = .{ .type = .INT, .glsl_name = "row_count" };
desc.uniform_blocks[0].glsl_uniforms[4] = .{ .type = .INT, .glsl_name = "viewport_height" };
desc.uniform_blocks[0].glsl_uniforms[5] = .{ .type = .INT, .glsl_name = "cursor_col" };
desc.uniform_blocks[0].glsl_uniforms[6] = .{ .type = .INT, .glsl_name = "cursor_row" };
desc.uniform_blocks[0].glsl_uniforms[7] = .{ .type = .INT, .glsl_name = "cursor_shape" };
desc.uniform_blocks[0].glsl_uniforms[8] = .{ .type = .INT, .glsl_name = "cursor_vis" };
desc.uniform_blocks[0].glsl_uniforms[9] = .{ .type = .FLOAT4, .glsl_name = "cursor_color" };
desc.uniform_blocks[0].glsl_uniforms[10] = .{ .type = .FLOAT4, .glsl_name = "sec_cursor_color" };
// Glyph atlas texture: R8 sample_type = FLOAT
desc.views[0].texture = .{

View file

@ -18,6 +18,16 @@ pub const Cell = gui_cell.Cell;
pub const Color = gui_cell.Rgba8;
const Rgba8 = gui_cell.Rgba8;
pub const CursorShape = enum(i32) { block = 0, beam = 1, underline = 2 };
pub const CursorInfo = struct {
vis: bool = false,
row: u16 = 0,
col: u16 = 0,
shape: CursorShape = .block,
color: Color = Color.initRgb(255, 255, 255),
};
const log = std.log.scoped(.gpu);
// Maximum glyph atlas dimension. 4096 is universally supported and gives
@ -350,6 +360,8 @@ pub fn paint(
col_count: u16,
top: u16,
cells: []const Cell,
cursor: CursorInfo,
secondary_cursors: []const CursorInfo,
) void {
const shader_col_count: u16 = @intCast(@divTrunc(client_size.x, font.cell_size.x));
const shader_row_count: u16 = @intCast(@divTrunc(client_size.y, font.cell_size.y));
@ -388,6 +400,13 @@ pub fn paint(
}
}
// Mark secondary cursor cells in the _pad field (read by fragment shader).
for (secondary_cursors) |sc| {
if (!sc.vis) continue;
if (sc.row >= shader_row_count or sc.col >= shader_col_count) continue;
shader_cells[@as(usize, sc.row) * shader_col_count + sc.col]._pad = 1;
}
// Upload glyph atlas to GPU if any new glyphs were rasterized this frame.
if (state.glyph_atlas_dirty) flushGlyphAtlas(state);
@ -429,12 +448,23 @@ pub fn paint(
bindings.samplers[1] = global.cell_sampler;
sg.applyBindings(bindings);
const sec_color: Color = if (secondary_cursors.len > 0)
secondary_cursors[0].color
else
Color.initRgb(255, 255, 255);
const fs_params = builtin_shader.FsParams{
.cell_size_x = font.cell_size.x,
.cell_size_y = font.cell_size.y,
.col_count = shader_col_count,
.row_count = shader_row_count,
.viewport_height = @intCast(client_size.y),
.cursor_col = cursor.col,
.cursor_row = cursor.row,
.cursor_shape = @intFromEnum(cursor.shape),
.cursor_vis = if (cursor.vis) 1 else 0,
.cursor_color = colorToVec4(cursor.color),
.sec_cursor_color = colorToVec4(sec_color),
};
sg.applyUniforms(0, .{
.ptr = &fs_params,
@ -446,6 +476,15 @@ pub fn paint(
// Note: caller (app.zig) calls sg.commit() and window.swapBuffers()
}
fn colorToVec4(c: Color) [4]f32 {
return .{
@as(f32, @floatFromInt(c.r)) / 255.0,
@as(f32, @floatFromInt(c.g)) / 255.0,
@as(f32, @floatFromInt(c.b)) / 255.0,
@as(f32, @floatFromInt(c.a)) / 255.0,
};
}
fn oom(e: error{OutOfMemory}) noreturn {
@panic(@errorName(e));
}

View file

@ -22,6 +22,12 @@ const gui_config = @import("gui_config");
const log = std.log.scoped(.wio_app);
// Re-export cursor types so renderer.zig (which imports 'app' but not 'gpu')
// can use them without a direct dependency on the gpu module.
pub const CursorInfo = gpu.CursorInfo;
pub const CursorShape = gpu.CursorShape;
pub const GpuColor = gpu.Color;
// Shared state (protected by screen_mutex)
const ScreenSnapshot = struct {
@ -31,6 +37,9 @@ const ScreenSnapshot = struct {
widths: []u8,
width: u16,
height: u16,
// Cursor state (set by renderer thread, consumed by wio thread)
cursor: gpu.CursorInfo,
secondary_cursors: []gpu.CursorInfo, // heap-allocated, freed with snapshot
};
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
@ -92,7 +101,11 @@ pub fn stop() void {
}
/// Called from the tui thread to push a new screen to the GPU thread.
pub fn updateScreen(vx_screen: *const vaxis.Screen) void {
pub fn updateScreen(
vx_screen: *const vaxis.Screen,
cursor: gpu.CursorInfo,
secondary_cursors: []const gpu.CursorInfo,
) void {
const allocator = gpa.allocator();
const cell_count: usize = @as(usize, vx_screen.width) * @as(usize, vx_screen.height);
@ -106,6 +119,13 @@ pub fn updateScreen(vx_screen: *const vaxis.Screen) void {
allocator.free(new_codepoints);
return;
};
const new_sec = allocator.alloc(gpu.CursorInfo, secondary_cursors.len) catch {
allocator.free(new_cells);
allocator.free(new_codepoints);
allocator.free(new_widths);
return;
};
@memcpy(new_sec, secondary_cursors);
// Convert vaxis cells gpu.Cell (colours only; glyph indices filled on GPU thread).
for (vx_screen.buf[0..cell_count], new_cells, new_codepoints, new_widths) |*vc, *gc, *cp, *wt| {
@ -131,6 +151,7 @@ pub fn updateScreen(vx_screen: *const vaxis.Screen) void {
allocator.free(old.cells);
allocator.free(old.codepoints);
allocator.free(old.widths);
allocator.free(old.secondary_cursors);
}
screen_snap = .{
.cells = new_cells,
@ -138,6 +159,8 @@ pub fn updateScreen(vx_screen: *const vaxis.Screen) void {
.widths = new_widths,
.width = vx_screen.width,
.height = vx_screen.height,
.cursor = cursor,
.secondary_cursors = new_sec,
};
screen_pending.store(true, .release);
@ -511,8 +534,14 @@ fn wioLoop() void {
const cp = pixelToCellPos(mouse_pos);
tui_pid.send(.{ "RDR", "B", @as(u8, 1), btn_id, cp.col, cp.row, cp.xoff, cp.yoff }) catch {};
},
.focused => window.enableTextInput(.{}),
.unfocused => window.disableTextInput(),
.focused => {
window.enableTextInput(.{});
tui_pid.send(.{"focus_in"}) catch {};
},
.unfocused => {
window.disableTextInput();
tui_pid.send(.{"focus_out"}) catch {};
},
else => {},
}
}
@ -561,6 +590,7 @@ fn wioLoop() void {
allocator.free(s.cells);
allocator.free(s.codepoints);
allocator.free(s.widths);
allocator.free(s.secondary_cursors);
}
state.size = .{ .x = win_size.width, .y = win_size.height };
@ -595,6 +625,8 @@ fn wioLoop() void {
s.width,
0,
cells_with_glyphs,
s.cursor,
s.secondary_cursors,
);
sg.commit();
window.swapBuffers();

View file

@ -3,7 +3,7 @@ pub const log_name = "renderer";
const std = @import("std");
const cbor = @import("cbor");
const vaxis = @import("vaxis");
pub const vaxis = @import("vaxis");
const Style = @import("theme").Style;
const Color = @import("theme").Color;
pub const CursorShape = vaxis.Cell.CursorShape;
@ -56,6 +56,20 @@ dispatch_event: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
thread: ?std.Thread = null,
window_ready: bool = false,
cursor_info: app.CursorInfo = .{},
cursor_color: app.GpuColor = app.GpuColor.initRgb(255, 255, 255),
secondary_cursors: std.ArrayListUnmanaged(app.CursorInfo) = .{},
secondary_color: app.GpuColor = app.GpuColor.initRgb(255, 255, 255),
cursor_blink: bool = false,
blink_on: bool = true,
blink_epoch: i64 = 0,
blink_period_us: i64 = 500_000,
blink_idle_us: i64 = 15_000_000,
blink_last_change: i64 = 0,
prev_cursor: app.CursorInfo = .{},
prev_cursor_blink: bool = false,
const global = struct {
var init_called: bool = false;
};
@ -92,6 +106,7 @@ pub fn init(
.dispatch_initialized = dispatch_initialized,
};
result.vx.caps.unicode = .unicode;
result.vx.caps.multi_cursor = true;
result.vx.screen.width_method = .unicode;
return result;
}
@ -101,6 +116,7 @@ pub fn deinit(self: *Self) void {
var drop: std.Io.Writer.Discarding = .init(&.{});
self.vx.deinit(self.allocator, &drop.writer);
self.event_buffer.deinit();
self.secondary_cursors.deinit(self.allocator);
}
pub fn run(self: *Self) Error!void {
@ -121,9 +137,47 @@ fn fmtmsg(self: *Self, value: anytype) std.Io.Writer.Error![]const u8 {
return self.event_buffer.written();
}
pub fn render(self: *Self) error{}!void {
if (!self.window_ready) return;
app.updateScreen(&self.vx.screen);
pub fn render(self: *Self) error{}!bool {
if (!self.window_ready) return false;
var cursor = self.cursor_info;
// Detect changes since the last rendered frame. Reset blink epoch and idle
// timer on any meaningful change so the cursor snaps to visible immediately.
if (cursor.vis != self.prev_cursor.vis or
cursor.row != self.prev_cursor.row or
cursor.col != self.prev_cursor.col or
cursor.shape != self.prev_cursor.shape or
self.cursor_blink != self.prev_cursor_blink)
{
const now = std.time.microTimestamp();
if (cursor.vis) {
self.blink_epoch = now;
self.blink_on = true;
}
self.blink_last_change = now;
}
self.prev_cursor = cursor;
self.prev_cursor_blink = self.cursor_blink;
// Apply blink unless the cursor has been idle for too long.
if (cursor.vis and self.cursor_blink) {
const now = std.time.microTimestamp();
const idle = now - self.blink_last_change;
if (idle < self.blink_idle_us) {
const elapsed = @mod(now - self.blink_epoch, self.blink_period_us * 2);
self.blink_on = elapsed < self.blink_period_us;
cursor.vis = self.blink_on;
} else {
cursor.vis = true; // freeze visible after idle timeout
}
}
app.updateScreen(&self.vx.screen, cursor, self.secondary_cursors.items);
if (!self.cursor_info.vis or !self.cursor_blink) return false;
const idle = std.time.microTimestamp() - self.blink_last_change;
return idle < self.blink_idle_us;
}
pub fn sigwinch(self: *Self) !void {
@ -414,13 +468,11 @@ pub fn get_fontfaces(self: *Self) void {
}
pub fn set_terminal_cursor_color(self: *Self, color: Color) void {
_ = self;
_ = color;
self.cursor_color = themeColorToGpu(color);
}
pub fn set_terminal_secondary_cursor_color(self: *Self, color: Color) void {
_ = self;
_ = color;
self.secondary_color = themeColorToGpu(color);
}
pub fn set_terminal_working_directory(self: *Self, absolute_path: []const u8) void {
@ -462,24 +514,56 @@ pub fn request_mouse_cursor_default(self: *Self, push_or_pop: bool) void {
}
pub fn cursor_enable(self: *Self, y: i32, x: i32, shape: CursorShape) !void {
_ = self;
_ = y;
_ = x;
_ = shape;
self.cursor_blink = isBlink(shape);
self.cursor_info = .{
.vis = true,
.row = if (y < 0) 0 else @intCast(y),
.col = if (x < 0) 0 else @intCast(x),
.shape = vaxisCursorShape(shape),
.color = self.cursor_color,
};
}
pub fn cursor_disable(self: *Self) void {
_ = self;
self.cursor_info.vis = false;
}
pub fn clear_all_multi_cursors(self: *Self) !void {
_ = self;
self.secondary_cursors.clearRetainingCapacity();
}
pub fn show_multi_cursor_yx(self: *Self, y: i32, x: i32) !void {
_ = self;
_ = y;
_ = x;
try self.secondary_cursors.append(self.allocator, .{
.vis = true,
.row = if (y < 0) 0 else @intCast(y),
.col = if (x < 0) 0 else @intCast(x),
.shape = self.cursor_info.shape,
.color = self.secondary_color,
});
}
fn themeColorToGpu(color: Color) app.GpuColor {
return .{
.r = @truncate(color.color >> 16),
.g = @truncate(color.color >> 8),
.b = @truncate(color.color),
.a = color.alpha,
};
}
fn isBlink(shape: CursorShape) bool {
return switch (shape) {
.default, .block_blink, .beam_blink, .underline_blink => true,
else => false,
};
}
fn vaxisCursorShape(shape: CursorShape) app.CursorShape {
return switch (shape) {
.default, .block, .block_blink => .block,
.beam, .beam_blink => .beam,
.underline, .underline_blink => .underline,
};
}
pub fn copy_to_system_clipboard(self: *Self, text: []const u8) void {

View file

@ -219,10 +219,11 @@ pub fn run(self: *Self) Error!void {
try self.loop.start();
}
pub fn render(self: *Self) !void {
if (in_panic.load(.acquire)) return;
pub fn render(self: *Self) !bool {
if (in_panic.load(.acquire)) return false;
try self.vx.render(self.tty.writer());
try self.tty.writer().flush();
return false;
}
pub fn sigwinch(self: *Self) !void {

View file

@ -165,9 +165,10 @@ fn fmtmsg(self: *Self, value: anytype) std.Io.Writer.Error![]const u8 {
return self.event_buffer.written();
}
pub fn render(self: *Self) error{}!void {
const hwnd = self.hwnd orelse return;
pub fn render(self: *Self) error{}!bool {
const hwnd = self.hwnd orelse return false;
_ = gui.updateScreen(hwnd, &self.vx.screen);
return false;
}
pub fn stop(self: *Self) void {
// this is guaranteed because stop won't be called until after

View file

@ -51,7 +51,7 @@ pub fn Options(context: type) type {
}
if (self.cursor) |cursor| {
const pos: c_int = @intCast(cursor);
if (tui.config().enable_terminal_cursor) {
if (tui.has_native_cursor()) {
const y, const x = self.plane.rel_yx_to_abs(0, pos + self.opts.padding + self.icon_width);
tui.rdr().cursor_enable(y, x, tui.get_cursor_shape()) catch {};
} else {

View file

@ -376,7 +376,7 @@ pub const Editor = struct {
animation_lag: f64,
animation_last_time: i64,
enable_terminal_cursor: bool,
software_rendered_cursor: bool,
render_whitespace: WhitespaceMode,
indent_size: usize,
tab_width: usize,
@ -639,7 +639,7 @@ pub const Editor = struct {
.animation_frame_rate = frame_rate,
.animation_last_time = time.microTimestamp(),
.enable_format_on_save = tui.config().enable_format_on_save,
.enable_terminal_cursor = tui.config().enable_terminal_cursor,
.software_rendered_cursor = !tui.has_native_cursor(),
.render_whitespace = tui.config().whitespace_mode,
};
self.add_default_symbol_triggers();
@ -1307,7 +1307,7 @@ pub const Editor = struct {
fn render_cursors(self: *Self, theme: *const Widget.Theme, cell_map: CellMap, focused: bool) !void {
const frame = tracy.initZone(@src(), .{ .name = "editor render cursors" });
defer frame.deinit();
if (focused and tui.config().enable_terminal_cursor and tui.rdr().vx.caps.multi_cursor)
if (focused and !self.software_rendered_cursor and tui.rdr().vx.caps.multi_cursor)
tui.rdr().clear_all_multi_cursors() catch {};
for (self.cursels.items[0 .. self.cursels.items.len - 1]) |*cursel_| if (cursel_.*) |*cursel| {
const cursor = cursel.cursor;
@ -1332,7 +1332,7 @@ pub const Editor = struct {
const focused = focused_ or self.cursor_focus_override;
if (focused and self.enable_terminal_cursor) {
if (focused and !self.software_rendered_cursor) {
if (screen_pos) |pos| {
self.render_term_cursor(pos, cursor_shape);
} else if (tui.is_mainview_focused() and tui.rdr().vx.caps.multi_cursor and self.has_secondary_cursors()) {
@ -1345,7 +1345,7 @@ pub const Editor = struct {
fn render_cursor_secondary(self: *Self, cursor: *const Cursor, theme: *const Widget.Theme, cell_map: CellMap, focused: bool) !void {
const pos = self.screen_cursor(cursor) orelse return;
set_cell_map_cursor(cell_map, pos.row, pos.col);
if (focused and self.enable_terminal_cursor and tui.rdr().vx.caps.multi_cursor)
if (focused and !self.software_rendered_cursor and tui.rdr().vx.caps.multi_cursor)
self.render_term_cursor_secondary(pos)
else
self.render_soft_cursor(pos, theme.editor_cursor_secondary);

View file

@ -200,7 +200,7 @@ pub fn on_render_menu(self: *Type, button: *Type.ButtonType, theme: *const Widge
const icon_: []const u8 = values.kind.icon();
const color: u24 = 0x0;
if (tui.config().enable_terminal_cursor) blk: {
if (tui.has_native_cursor()) blk: {
const cursor = self.value.editor.get_primary_abs() orelse break :blk;
tui.rdr().cursor_enable(@intCast(cursor.row), @intCast(cursor.col), tui.get_cursor_shape()) catch {};
}

View file

@ -117,7 +117,7 @@ fn render_mini_mode(plane: *Plane, theme: *const Widget.Theme) void {
_ = plane.putstr_unicode(mini_mode.text) catch {};
if (mini_mode.cursor) |cursor| {
const pos: c_int = @intCast(cursor);
if (tui.config().enable_terminal_cursor) {
if (tui.has_native_cursor()) {
const y, const x = plane.rel_yx_to_abs(0, pos + 1);
tui.rdr().cursor_enable(y, x, tui.get_cursor_shape()) catch {};
} else {

View file

@ -87,6 +87,7 @@ pub fn run_cmd(self: *Self, ctx: command.Context) !void {
var argv_list: std.ArrayListUnmanaged([]const u8) = .empty;
defer argv_list.deinit(self.allocator);
var have_cmd = false;
if (argv_msg) |msg| {
var iter = msg.buf;
var len = try cbor.decodeArrayHeader(&iter);
@ -94,6 +95,7 @@ pub fn run_cmd(self: *Self, ctx: command.Context) !void {
var arg: []const u8 = undefined;
if (try cbor.matchValue(&iter, cbor.extract(&arg)))
try argv_list.append(self.allocator, arg);
have_cmd = true;
}
} else {
const default_shell = if (builtin.os.tag == .windows)
@ -110,7 +112,7 @@ pub fn run_cmd(self: *Self, ctx: command.Context) !void {
const rows: u16 = @intCast(@max(24, self.plane.dim_y()));
if (global_vt) |*vt| {
if (!vt.process_exited) {
if (!vt.process_exited and have_cmd) {
var msg: std.Io.Writer.Allocating = .init(self.allocator);
defer msg.deinit();
try msg.writer.writeAll("terminal is already running '");
@ -118,10 +120,9 @@ pub fn run_cmd(self: *Self, ctx: command.Context) !void {
try msg.writer.writeAll("'");
return tp.exit(msg.written());
}
vt.deinit(self.allocator);
global_vt = null;
} else {
try Vt.init(self.allocator, argv_list.items, env, rows, cols, on_exit);
}
try Vt.init(self.allocator, argv_list.items, env, rows, cols, on_exit);
self.vt = &global_vt.?;
if (self.last_cmd) |cmd| {
@ -379,7 +380,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
}
// Blit the terminal's front screen into our vaxis.Window.
const software_cursor = build_options.gui or !tui.config().enable_terminal_cursor;
const software_cursor = !tui.has_native_cursor();
const focused_cursor_color: ?[3]u8 = if (theme.editor_cursor.bg) |bg| RGB.to_u8s(RGB.from_u24(bg.color)) else null;
const unfocused_cursor_color: ?[3]u8 = if (theme.editor_cursor_secondary.bg) |bg| RGB.to_u8s(RGB.from_u24(bg.color)) else focused_cursor_color;
self.vt.vt.draw(self.allocator, self.plane.window, self.focused and tui.terminal_has_focus(), software_cursor, focused_cursor_color, unfocused_cursor_color) catch |e| {

View file

@ -164,7 +164,6 @@ fn init(allocator: Allocator) InitError!*Self {
conf.theme = dark_theme.name;
const light_theme = Widget.get_theme_by_name(allocator, conf.light_theme) orelse Widget.get_theme_by_name(allocator, "default-light") orelse return error.UnknownTheme;
conf.light_theme = light_theme.name;
if (build_options.gui) conf.enable_terminal_cursor = false;
const frame_rate: usize = @intCast(tp.env.get().num("frame-rate"));
if (frame_rate != 0)
@ -659,7 +658,7 @@ fn render(self: *Self) void {
defer frame.deinit();
self.rdr_.stdplane().erase();
const theme_ = self.current_theme();
if (self.config_.enable_terminal_cursor) {
if (has_native_cursor()) {
self.rdr_.cursor_disable();
if (self.rdr_.vx.caps.multi_cursor) self.rdr_.clear_all_multi_cursors() catch {};
self.rdr_.set_terminal_cursor_color(theme_.editor_cursor.bg.?);
@ -682,13 +681,17 @@ fn render(self: *Self) void {
top_layer_.draw(self.rdr_.stdplane());
}
{
const renderer_more = ret: {
const frame = tracy.initZone(@src(), .{ .name = renderer.log_name ++ " render" });
defer frame.deinit();
self.rdr_.render() catch |e| self.logger.err("render", e);
const m = self.rdr_.render() catch |e| blk: {
self.logger.err("render", e);
break :blk false;
};
tracy.frameMark();
self.unrendered_input_events_count = 0;
}
break :ret m;
};
self.top_layer_reset();
self.idle_frame_count = if (self.unrendered_input_events_count > 0)
@ -696,7 +699,7 @@ fn render(self: *Self) void {
else
self.idle_frame_count + 1;
if (more or self.idle_frame_count < idle_frames or self.no_sleep) {
if (more or renderer_more or self.idle_frame_count < idle_frames or self.no_sleep) {
if (!self.frame_clock_running) {
self.frame_clock.start() catch {};
self.frame_clock_running = true;
@ -2171,7 +2174,12 @@ pub fn get_cursor_shape() renderer.CursorShape {
default_cursor
else
default_cursor;
const shape = if (self.rdr_.vx.caps.multi_cursor and shape_ == .default) .beam_blink else shape_;
const shape = if (build_options.gui and shape_ == .default)
.beam
else if (self.rdr_.vx.caps.multi_cursor and shape_ == .default)
.beam_blink
else
shape_;
return switch (shape) {
.default => .default,
.block_blink => .block_blink,
@ -2190,6 +2198,10 @@ pub fn is_cursor_beam() bool {
};
}
pub fn has_native_cursor() bool {
return current().config_.enable_terminal_cursor;
}
pub fn get_selection_style() @import("Buffer").Selection.Style {
return if (current().input_mode_) |mode| mode.selection_style else .normal;
}

View file

@ -5,7 +5,7 @@ const win32 = @import("win32").everything;
const win32ext = @import("win32ext.zig");
const dwrite = @import("dwrite.zig");
const XY = @import("../gui/xy.zig").XY;
const XY = @import("xy").XY;
pub const Font = dwrite.Font;
pub const Fonts = dwrite.Fonts;

View file

@ -4,11 +4,11 @@ const win32 = @import("win32").everything;
const win32ext = @import("win32ext.zig");
const dwrite = @import("dwrite.zig");
const GlyphIndexCache = @import("../gui/GlyphIndexCache.zig");
const GlyphIndexCache = @import("GlyphIndexCache");
const TextRenderer = @import("DwriteRenderer.zig");
const XY = @import("../gui/xy.zig").XY;
const gui_cell = @import("../gui/Cell.zig");
const XY = @import("xy").XY;
const gui_cell = @import("Cell");
pub const Font = TextRenderer.Font;
pub const Fonts = TextRenderer.Fonts;

View file

@ -2,7 +2,7 @@ const std = @import("std");
const win32 = @import("win32").everything;
const FontFace = @import("FontFace.zig");
const XY = @import("xy.zig").XY;
const XY = @import("xy").XY;
const global = struct {
var init_called: bool = false;

View file

@ -18,10 +18,10 @@ const input = @import("input");
const windowmsg = @import("windowmsg.zig");
const render = @import("d3d11.zig");
const xterm = @import("../gui/xterm.zig");
const xterm = @import("xterm");
const FontFace = @import("FontFace.zig");
const XY = @import("../gui/xy.zig").XY;
const XY = @import("xy").XY;
const WM_APP_EXIT = win32.WM_APP + 1;
const WM_APP_SET_BACKGROUND = win32.WM_APP + 2;