summaryrefslogtreecommitdiff
path: root/src/gui/gui.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/gui.zig')
-rw-r--r--src/gui/gui.zig110
1 files changed, 91 insertions, 19 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 17bdb46f..7f28124c 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -2111,8 +2111,8 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options, attach: ?[]const u
}
var win_flags: c.SDL_WindowFlags = c.SDL_WINDOW_RESIZABLE;
if (!test_mode) win_flags |= c.SDL_WINDOW_HIGH_PIXEL_DENSITY;
- // A latency run renders to the capture texture; keep it off the desktop.
- if (latency_fd >= 0) win_flags |= c.SDL_WINDOW_HIDDEN;
+ // A test run renders to the capture texture; keep it off the desktop.
+ if (test_mode) win_flags |= c.SDL_WINDOW_HIDDEN;
const native_wayland = !test_mode and std.mem.eql(u8, std.mem.span(c.SDL_GetCurrentVideoDriver()), "wayland");
// Background-only opacity needs per-pixel alpha; whole-window compositor
// opacity would dim the text as well. SDL requires this flag at creation.
@@ -2353,6 +2353,7 @@ fn localSession(
} else try pardes.Pardes.init(allocs.pardes, opts);
defer core.deinit();
core.native_images = true;
+ core.native_image_patches = true;
observeGuiFont(g, core);
syncTaglineFont(g, core);
@@ -2448,7 +2449,7 @@ fn localSession(
}
}
if (core.takeRestore()) |rp| blk: {
- const bytes = filesystem.readRestore(gpa, rp) catch |err| {
+ const bytes = filesystem.readRestore(gpa, rp, core.settings.dump_dir.get()) catch |err| {
core.reportError(core.active, "Restore", err);
break :blk;
};
@@ -2473,6 +2474,7 @@ fn localSession(
g.presented_images.clearRetainingCapacity();
g.prepared_images.clearRetainingCapacity();
nc.native_images = true;
+ nc.native_image_patches = true;
nc.host = host;
if (fs) |f| f.reset(nc);
core.deinit();
@@ -3591,6 +3593,7 @@ const Shell = struct {
test_mode: bool = false,
feed: StdinFeed = .{},
presented: bool = false,
+ skipped_presents: u8 = 0,
surface: ?*pardes.Surface = null,
animation_clock: AnimationClock = .{},
saw_event: bool = false,
@@ -3774,14 +3777,29 @@ fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void {
var in: Input = .{ .core = core };
if (s.gui) |g| {
var sev = std.mem.zeroes(c.SDL_Event);
- const ms: c_int = if (timeout_ms != 0) @intCast(timeout_ms) else 16;
+ // A latency trace is fed on stdin, whose poll below paces the loop:
+ // waiting here too would make every tick cost two frames.
+ const ms: c_int = if (latency_fd >= 0) 0 else if (timeout_ms != 0) @intCast(timeout_ms) else 16;
// The wait is the 9P connections' turn with the core.
pardes.turn.rest();
const got = c.SDL_WaitEventTimeout(&sev, ms);
pardes.turn.wake();
if (got) {
- dispatch(g, &in, &sev);
- while (c.SDL_PollEvent(&sev)) dispatch(g, &in, &sev);
+ // A run of queued motions is one move to its last position (the
+ // handler reads only absolute x/y and held buttons): acting on
+ // each would repeat hover and selection work the next undoes.
+ var motion: ?c.SDL_Event = null;
+ while (true) {
+ if (sev.type == c.SDL_EVENT_MOUSE_MOTION) {
+ motion = sev;
+ } else {
+ if (motion) |*m| dispatch(g, &in, m);
+ motion = null;
+ dispatch(g, &in, &sev);
+ }
+ if (!c.SDL_PollEvent(&sev)) break;
+ }
+ if (motion) |*m| dispatch(g, &in, m);
}
}
if (s.test_mode) {
@@ -3913,6 +3931,10 @@ fn present(ctx: ?*anyopaque, surface: *const pardes.Surface) void {
log.err("render: {t}", .{err});
break :blk false;
};
+ // A skipped swapchain image would otherwise hide this input's frame until
+ // the next event. A few retries only: a minimized window never gets one.
+ s.skipped_presents = if (s.presented) 0 else s.skipped_presents +| 1;
+ if (!s.presented and s.skipped_presents <= 3) core.present_skipped = true;
if (g.scene_target_failed) {
g.scene_failures +|= 1;
log.err("scene target unavailable (attempt {d}/3)", .{g.scene_failures});
@@ -3926,7 +3948,12 @@ fn present(ctx: ?*anyopaque, surface: *const pardes.Surface) void {
fn postPresent(ctx: ?*anyopaque) void {
const s = shellOf(ctx);
const g = s.gui orelse return;
- if (!s.presented) return;
+ if (!s.presented) {
+ // No image (a minimized window): time still passes, so a lingering
+ // message or a hover wait runs out instead of redrawing forever.
+ if (s.animation_clock.due(s.core.animationActive(), c.SDL_GetTicksNS())) s.core.update(.tick);
+ return;
+ }
const frame = s.surface orelse return;
refreshPresentedPointer(g, s.core);
finishPresentedAnimationFrame(
@@ -4030,7 +4057,7 @@ fn writeFile(ctx: ?*anyopaque, pane: u8, path: []const u8, bytes: []const u8) vo
fn writeDump(ctx: ?*anyopaque, bytes: []const u8) void {
const s = shellOf(ctx);
var pbuf: [1024:0]u8 = undefined;
- const path = pardes.dump.outPath(&pbuf) orelse return;
+ const path = pardes.dump.outPath(&pbuf, s.core.settings.dump_dir.get()) orelse return;
filesystem.write(s.core, path, bytes) catch |err| return s.core.reportError(0, "dump", err);
s.core.setLastDump(path);
}
@@ -4533,6 +4560,23 @@ fn uploadNativeTexture(
const byte_len = validImageBytes(place) orelse return error.BadImage;
const key = place.cacheKey();
if (g.native_images.contains(key)) return;
+ if (comptime pardes.pdf_enabled) if (place.patch) |patch| patch: {
+ // The core changed only these rows since a revision we still hold:
+ // write them into that texture rather than sending the whole page.
+ var base = key;
+ base.revision = patch.from;
+ if (patch.y + patch.h > place.ih) break :patch;
+ const texture = g.native_images.get(base) orelse break :patch;
+ const row = place.iw * 4;
+ try uploadTextureRows(g, cmd, texture, place.rgba[patch.y * row ..][0 .. patch.h * row], place.iw, patch.y, patch.h);
+ try g.native_images.put(gpa, key, texture);
+ _ = g.native_images.remove(base);
+ // A transition snapshot of the last frame follows its texture.
+ for (g.presented_images.items) |*saved| if (saved.key.eql(base)) {
+ saved.key = key;
+ };
+ return;
+ };
var tex_info = std.mem.zeroes(c.SDL_GPUTextureCreateInfo);
tex_info.type = c.SDL_GPU_TEXTURETYPE_2D;
@@ -4545,39 +4589,51 @@ fn uploadNativeTexture(
tex_info.sample_count = c.SDL_GPU_SAMPLECOUNT_1;
const texture = c.SDL_CreateGPUTexture(g.device, &tex_info) orelse return error.GpuCreate;
errdefer c.SDL_ReleaseGPUTexture(g.device, texture);
+ try uploadTextureRows(g, cmd, texture, place.rgba[0..byte_len], place.iw, 0, place.ih);
+ try g.native_images.put(gpa, key, texture);
+}
+/// Copy `rows` rows of RGBA, `width` pixels each, into `texture` at row `y`.
+fn uploadTextureRows(
+ g: *Gui,
+ cmd: *c.SDL_GPUCommandBuffer,
+ texture: *c.SDL_GPUTexture,
+ rgba: []const u8,
+ width: usize,
+ y: usize,
+ rows: usize,
+) !void {
var xf_info = c.SDL_GPUTransferBufferCreateInfo{
.usage = c.SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
- .size = byte_len,
+ .size = @intCast(rgba.len),
.props = 0,
};
const transfer = c.SDL_CreateGPUTransferBuffer(g.device, &xf_info) orelse return error.GpuCreate;
defer c.SDL_ReleaseGPUTransferBuffer(g.device, transfer);
const mapped: [*]u8 = @ptrCast(c.SDL_MapGPUTransferBuffer(g.device, transfer, false) orelse return error.GpuMap);
- @memcpy(mapped[0..byte_len], place.rgba);
+ @memcpy(mapped[0..rgba.len], rgba);
c.SDL_UnmapGPUTransferBuffer(g.device, transfer);
const copy = c.SDL_BeginGPUCopyPass(cmd);
const src = c.SDL_GPUTextureTransferInfo{
.transfer_buffer = transfer,
.offset = 0,
- .pixels_per_row = @intCast(place.iw),
- .rows_per_layer = @intCast(place.ih),
+ .pixels_per_row = @intCast(width),
+ .rows_per_layer = @intCast(rows),
};
const dst = c.SDL_GPUTextureRegion{
.texture = texture,
.mip_level = 0,
.layer = 0,
.x = 0,
- .y = 0,
+ .y = @intCast(y),
.z = 0,
- .w = @intCast(place.iw),
- .h = @intCast(place.ih),
+ .w = @intCast(width),
+ .h = @intCast(rows),
.d = 1,
};
c.SDL_UploadToGPUTexture(copy, &src, &dst, false);
c.SDL_EndGPUCopyPass(copy);
- try g.native_images.put(gpa, key, texture);
}
fn appendPreparedImage(
@@ -5442,16 +5498,30 @@ fn emitTagLayer(g: *Gui, instances: [*]CellInstance, next: *u32, layer: *const p
const left = @as(f32, @floatFromInt(layer.viewport.x)) * bw;
const right = left + @as(f32, @floatFromInt(layer.viewport.w)) * bw;
const background: pardes.Cell = .{ .default = false, .style = .{ .bg = .{ .rgb = layer.bg } } };
- emitInstance(g, instances, next.*, 0, layer.viewport.y, .{ .w = right - left, .h = bh, .x_off = left, .y_off = 0 }, win_w, win_h, track, .body, &background, old_layer, false, page);
+ // A band sliding into place is drawn offset and cut at its row's top, so
+ // it comes out from under whatever is above it.
+ const slide = layer.slide * bh;
+ const top = @as(f32, @floatFromInt(layer.viewport.y)) * bh;
+ emitInstance(g, instances, next.*, 0, layer.viewport.y, .{ .w = right - left, .h = bh, .x_off = left, .y_off = slide }, win_w, win_h, track, .body, &background, old_layer, false, page);
+ if (slide != 0) clipInstanceTop(&instances[next.*], top, win_h);
next.* += 1;
for (0..tagLayerCellCount(g, layer)) |col| {
const cursor = if (layer.cursor) |cu| show_cursor and !cu.bar and cu.col == col else false;
- emitInstance(g, instances, next.*, @intCast(col), layer.viewport.y, .{ .w = tw, .h = bh, .x_off = left, .y_off = 0 }, win_w, win_h, track, .tagline, &layer.cells[col], old_layer, cursor, page);
+ emitInstance(g, instances, next.*, @intCast(col), layer.viewport.y, .{ .w = tw, .h = bh, .x_off = left, .y_off = slide }, win_w, win_h, track, .tagline, &layer.cells[col], old_layer, cursor, page);
clipInstanceRight(&instances[next.*], right, win_w);
+ if (slide != 0) clipInstanceTop(&instances[next.*], top, win_h);
next.* += 1;
}
}
+fn clipInstanceTop(instance: *CellInstance, top: f32, win_h: f32) void {
+ const limit = 1 - top / win_h * 2;
+ if (instance.y0 <= limit) return;
+ const fraction = std.math.clamp((limit - instance.y1) / (instance.y0 - instance.y1), 0, 1);
+ instance.v0 = instance.v1 + (instance.v0 - instance.v1) * fraction;
+ instance.y0 = @max(instance.y1, limit);
+}
+
fn bodyLayerAt(surface: *const pardes.Surface, col: u16, row: u16) ?*const pardes.BodyLayer {
return bodyLayerIn(surface.bodyLayers(), col, row);
}
@@ -6436,7 +6506,9 @@ fn captureFrame(g: *Gui, gpa: std.mem.Allocator, cmd: *c.SDL_GPUCommandBuffer, t
}
fn writeCapturePpm(g: *Gui, gpa: std.mem.Allocator, pixels: []const u8, width: u32, height: u32) !void {
- if (g.capture_dir.len == 0) return;
+ // A latency trace times frames, and an 8 MB file write per frame would
+ // stall the loop it is timing.
+ if (g.capture_dir.len == 0 or latency_fd >= 0) return;
const bpp = c.SDL_GPUTextureFormatTexelBlockSize(g.swapchain_format);
if (bpp != 4) return error.UnsupportedCaptureFormat;
const bgr = switch (g.swapchain_format) {