Compare commits
9 commits
0564673946
...
8c9cee9fac
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c9cee9fac | |||
| 67595e676b | |||
| e1c90cbe26 | |||
| c48d592edc | |||
| 32ed60bc64 | |||
| 1c1886defc | |||
| 546cf1f6dc | |||
| b0d32f3581 | |||
| 908f780605 |
17 changed files with 320 additions and 115 deletions
111
build.zig
111
build.zig
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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 = .{
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue