diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 12:24:57 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 3921bfe63980552eddeaa930d5d921d9a2868b37 (patch) | |
| tree | d6b280abf3848f29b9a65a19aa2d010cc2d23660 /src/gui/gui.zig | |
| parent | ff0e9832570d9d73b7964a275b605567a0e412b4 (diff) | |
| parent | d6195b4ecc89a6506985e17313b580d570b03b77 (diff) | |
| download | pardes-3921bfe63980552eddeaa930d5d921d9a2868b37.tar.gz pardes-3921bfe63980552eddeaa930d5d921d9a2868b37.zip | |
Release Pardes 0.7
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/gui/gui.zig')
| -rw-r--r-- | src/gui/gui.zig | 99 |
1 files changed, 90 insertions, 9 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 8fd8d360..be961477 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -5872,6 +5872,7 @@ fn renderFrame( if (surface.chrome.fx & 2 == 0 or surface.panelTracks().len != 0) g.hover_glow = .{ .now_ns = g.sel_glow.now_ns }; if (g.glide.moving()) g.glide.offset = g.glide.spring.value(g.glide.now_ns); cell_decor = CellDecor.of(chrome); + plaque_through_len = 0; // G13: a body's dots follow its text at a quarter of its speed: its // first line, and a glide's picture offset, in rows. if (chrome.fx & 8 != 0 and chrome.decor_dots != null) for (surface.regionList()) |region| { @@ -6159,6 +6160,25 @@ fn renderFrame( for (groups.items[0..groups.len], 0..) |group, index| std.debug.assert(cell_next[index] == group.cell_start + group.cell_count); shifted = emitScrollRows(g, instances, cell_total, surface, layout, win_w, win_h, page); + // Over a theme's plaques a see-through cell with no ink blends to + // the plaque under it, so it draws nothing: its quad goes, and the + // cells are drawn unblended (a third of the GPU's frame at 4K) + // unless one see-through cell does have ink, or the page itself is + // see-through. + if (cell_decor.nplaques != 0) { + const space_u = @as(f32, @floatFromInt(g.space_slot.u)) / @as(f32, @floatFromInt(atlas_w)); + const space_v = @as(f32, @floatFromInt(g.space_slot.v)) / @as(f32, @floatFromInt(atlas_h)); + if (page.clear or plaque_through_len > plaque_through.len) cell_decor.inked_clear = true; + for (plaque_through[0..@min(plaque_through_len, plaque_through.len)]) |at| { + const instance = &instances[at]; + // A grip's button takes its cell's ground back (emitSurfaceCell). + if (instance.effect & clear_bg_bit == 0) continue; + if (instance.u0 == space_u and instance.v0 == space_v) { + instance.x1 = instance.x0; + instance.y1 = instance.y0; + } else cell_decor.inked_clear = true; + } + } @memcpy(instances[cell_total + shifted ..][0..decor_len], g.decor.items); c.SDL_UnmapGPUTransferBuffer(g.device, g.vxfer.?); @@ -6190,7 +6210,7 @@ fn renderFrame( // Blended over what is under: a theme's plaques show through // blank tag cells; a dissolve's old cells fade over its new. const dissolving = if (group.track) |active| active.effect == .dissolve else false; - c.SDL_BindGPUGraphicsPipeline(rp, if (cell_decor.nplaques != 0 or dissolving) g.pipeline_over else g.pipeline); + c.SDL_BindGPUGraphicsPipeline(rp, if (cell_decor.inked_clear or dissolving) g.pipeline_over else g.pipeline); const samp_binding = c.SDL_GPUTextureSamplerBinding{ .texture = g.atlas_tex, .sampler = g.atlas_sampler }; c.SDL_BindGPUFragmentSamplers(rp, 0, &samp_binding, 1); if (group.cell_count != 0) { @@ -6516,7 +6536,7 @@ fn emitTagLayer(g: *Gui, instances: [*]CellInstance, next: *u32, layer: *const p emitInstance(g, instances, next.*, 0, y, .{ .w = band_right - left, .h = bh, .x_off = left, .y_off = slide }, win_w, win_h, track, .body, (&background)[0..1], 0, null, old_layer, false, page); // A theme's plaque is drawn under the band: the band's own ground // lets it show (tagPlaques). - if (cell_decor.nplaques != 0 and cell_decor.inPlaque(left + 0.5, top + 0.5)) instances[next.*].effect |= clear_bg_bit; + if (cell_decor.nplaques != 0 and cell_decor.inPlaque(left + 0.5, top + 0.5)) plaqueThrough(instances, next.*); if (slide != 0) clipInstanceTop(&instances[next.*], top, win_h); next.* += 1; const cells = layer.cells[index * layer.cols ..][0..layer.cols]; @@ -6728,7 +6748,7 @@ fn emitSurfaceCell( const cell_x = @as(f32, @floatFromInt(col)) * body_layout.w + body_layout.x_off; const cell_y = @as(f32, @floatFromInt(row)) * body_layout.h + body_layout.y_off + 0.5; if (cell_decor.nplaques != 0 and cell_decor.inPlaque(if (here.grip) cell_x + body_layout.w - 0.5 else cell_x + 0.5, cell_y)) - instances[next.*].effect |= clear_bg_bit; + plaqueThrough(instances, next.*); next.* += 1; // Tag glyphs keep their font size inside a full physical-width grip. // A pane's grip cells are the cell shells' form (a dirty `*`): the @@ -7060,7 +7080,7 @@ fn emitInstance( if (cell_decor.nplaques != 0 and resolved.role == .tagline and cellCodepoint(&line[at]) == ' ' and (std.mem.eql(u8, &resolved.bg, &cell_decor.grounds[0]) or std.mem.eql(u8, &resolved.bg, &cell_decor.grounds[1])) and (cell_decor.inPlaque(px0 + 0.5, py0 + 0.5) or cell_decor.inPlaque(px0 + layout.w - 0.5, py0 + 0.5))) - instances[idx].effect |= clear_bg_bit; + plaqueThrough(instances, idx); if (cell_decor.dots) |dotted| { if (!resolved.clear_bg and std.mem.eql(u8, &resolved.bg, &dotted)) { instances[idx].effect |= dotted_bit; @@ -9034,6 +9054,17 @@ fn updateDisplayScale(window: *c.SDL_Window) void { /// it falls on, and pushLayerOpacity hands the shader its colours and /// sizes. ponytail: one window per process, as display_scale. var cell_decor: CellDecor = .{}; +/// The cells this frame lets a plaque show through, by instance, for +/// renderFrame to drop the ones with no ink. Past its length the frame's +/// cells are blended instead. +var plaque_through: [16384]u32 = undefined; +var plaque_through_len: usize = 0; + +fn plaqueThrough(instances: [*]CellInstance, at: u32) void { + instances[at].effect |= clear_bg_bit; + if (plaque_through_len < plaque_through.len) plaque_through[plaque_through_len] = at; + plaque_through_len += 1; +} const CellDecor = struct { /// The page's ground, when the theme dots it. dots: ?[3]u8 = null, @@ -9048,6 +9079,9 @@ const CellDecor = struct { nplaques: usize = 0, grounds: [2][3]u8 = .{ .{ 0, 0, 0 }, .{ 0, 0, 0 } }, plaques_off: bool = false, + /// A see-through cell over the plaques has ink of its own, so this + /// frame's cells are blended (renderFrame). + inked_clear: bool = false, /// G13: each body's rectangle and how far its dots have moved. bodies: [pardes.MAX_PANES][4]f32 = undefined, @@ -9296,12 +9330,15 @@ fn tagPlaques(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, g const b = plaque.border; const sh = plaque.shadow; const fill = if (tag.active) chrome.tag_focus_bg else chrome.tag_bg; - // The band's own ground, then the shadow, the frame and the fill. - try addDecor(g, gpa, plaque.band[0], plaque.band[1], plaque.band[2], plaque.band[3], page, 1, track, win_w, win_h); - const shadow_rgb = chrome.decor_box_shadow orelse border_rgb; - try addDecor(g, gpa, plaque.box[0] + sh[0], plaque.box[1] + sh[1], plaque.box[2] + sh[0], plaque.box[3] + sh[1], if (pane) shadow_rgb else mixRgb(shadow_rgb, page), 1, track, win_w, win_h); - try addDecor(g, gpa, plaque.box[0], plaque.box[1], plaque.box[2], plaque.box[3], border_rgb, 1, track, win_w, win_h); + // The band's own ground, then the shadow, the frame and the fill, + // each only where the ones over it leave it showing: all four are + // opaque, so that is the same picture drawn once, not four times. + const shadow: [4]f32 = .{ plaque.box[0] + sh[0], plaque.box[1] + sh[1], plaque.box[2] + sh[0], plaque.box[3] + sh[1] }; const inner: [4]f32 = .{ plaque.box[0] + b, plaque.box[1] + b, plaque.box[2] - b, plaque.box[3] - b }; + try addDecorExcept(g, gpa, plaque.band, &.{ plaque.box, shadow }, page, track, win_w, win_h); + const shadow_rgb = chrome.decor_box_shadow orelse border_rgb; + try addDecorExcept(g, gpa, shadow, &.{plaque.box}, if (pane) shadow_rgb else mixRgb(shadow_rgb, page), track, win_w, win_h); + try addDecorExcept(g, gpa, plaque.box, &.{inner}, border_rgb, track, win_w, win_h); if (pane and tag.active and chrome.decor_box_stripe != null) try addPattern(g, gpa, inner[0], inner[1], inner[2], inner[3], chrome.decor_box_stripe.?, fill, dp(@floatFromInt(@max(1, chrome.decor_box_stripe_px))), true, track, win_w, win_h) else @@ -9309,6 +9346,50 @@ fn tagPlaques(g: *Gui, gpa: std.mem.Allocator, regions: []const pardes.Region, g }; } +/// Flat decor over `rect` (window pixels) less each of `holes`: up to four +/// strips round the first hole's part of it, each less the rest. +fn addDecorExcept(g: *Gui, gpa: std.mem.Allocator, rect: [4]f32, holes: []const [4]f32, rgb: [3]u8, track: ?pardes.animation.Track, win_w: f32, win_h: f32) std.mem.Allocator.Error!void { + if (rect[0] >= rect[2] or rect[1] >= rect[3]) return; + if (holes.len == 0) return addDecor(g, gpa, rect[0], rect[1], rect[2], rect[3], rgb, 1, track, win_w, win_h); + const hole = holes[0]; + const x0 = @max(rect[0], hole[0]); + const y0 = @max(rect[1], hole[1]); + const x1 = @min(rect[2], hole[2]); + const y1 = @min(rect[3], hole[3]); + if (x0 >= x1 or y0 >= y1) return addDecorExcept(g, gpa, rect, holes[1..], rgb, track, win_w, win_h); + for ([_][4]f32{ + .{ rect[0], rect[1], rect[2], y0 }, + .{ rect[0], y1, rect[2], rect[3] }, + .{ rect[0], y0, x0, y1 }, + .{ x1, y0, rect[2], y1 }, + }) |strip| try addDecorExcept(g, gpa, strip, holes[1..], rgb, track, win_w, win_h); +} + +test "decor less its holes covers each pixel of the rest once and none of the holes" { + const gpa = std.testing.allocator; + var g: Gui = undefined; + g.cell_w = 1; + g.cell_h = 1; + g.decor = .empty; + defer g.decor.deinit(gpa); + const holes = [_][4]f32{ .{ 3, 2, 9, 6 }, .{ 5, 4, 12, 8 }, .{ 30, 30, 40, 40 } }; + try addDecorExcept(&g, gpa, .{ 1, 1, 11, 10 }, &holes, .{ 1, 2, 3 }, null, 16, 12); + for (0..12) |y| for (0..16) |x| { + const px: f32 = @as(f32, @floatFromInt(x)) + 0.5; + const py: f32 = @as(f32, @floatFromInt(y)) + 0.5; + var hits: usize = 0; + for (g.decor.items) |item| { + const r: [4]f32 = .{ (item.x0 + 1) / 2 * 16, (1 - item.y0) / 2 * 12, (item.x1 + 1) / 2 * 16, (1 - item.y1) / 2 * 12 }; + hits += @intFromBool(px > r[0] and px < r[2] and py > r[1] and py < r[3]); + } + const in_rect = px > 1 and px < 11 and py > 1 and py < 10; + const in_hole = for (holes) |h| { + if (px > h[0] and px < h[2] and py > h[1] and py < h[3]) break true; + } else false; + try std.testing.expectEqual(@as(usize, @intFromBool(in_rect and !in_hole)), hits); + }; +} + const Plaque = struct { band: [4]f32, box: [4]f32, border: f32, shadow: [2]f32 }; /// A tag's plaque in window pixels: its band (the rows, from after the |
