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.zig103
1 files changed, 90 insertions, 13 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 449cae15..b5da9ad6 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -5963,7 +5963,9 @@ fn renderFrame(
const track = groups.items[group_index].track;
const data_effect = if (track) |active| active.effect == .dissolve else false;
const data_diff = data_effect and gripCellChanged(surface, covered, previous_covered, col, row);
- if (data_diff) emitSurfaceCell(
+ // The new cell, then (a dissolve) the old one fading out over
+ // it, as the old layers come after the new ones.
+ emitSurfaceCell(
g,
chrome,
instances,
@@ -5973,16 +5975,16 @@ fn renderFrame(
layout,
win_w,
win_h,
- track,
- surface.previous_cells[@as(usize, row) * surface.cols ..][0..surface.cols],
- previous_covered[@as(usize, row) * surface.cols ..][0..surface.cols],
+ if (data_effect and !data_diff) null else track,
+ line,
+ line_cover,
col,
- null,
- true,
+ line_cursor,
false,
+ cursors_shown and at == cursor_idx and !cursor_bar and bodyLayerAt(surface, col, row) == null,
page,
);
- emitSurfaceCell(
+ if (data_diff) emitSurfaceCell(
g,
chrome,
instances,
@@ -5992,13 +5994,13 @@ fn renderFrame(
layout,
win_w,
win_h,
- if (data_effect and !data_diff) null else track,
- line,
- line_cover,
+ track,
+ surface.previous_cells[@as(usize, row) * surface.cols ..][0..surface.cols],
+ previous_covered[@as(usize, row) * surface.cols ..][0..surface.cols],
col,
- line_cursor,
+ null,
+ true,
false,
- cursors_shown and at == cursor_idx and !cursor_bar and bodyLayerAt(surface, col, row) == null,
page,
);
}
@@ -6104,7 +6106,10 @@ fn renderFrame(
}
const has_shifted = index == 0 and shifted != 0;
if ((group.cell_count != 0 or has_shifted) and g.vbuf != null) {
- c.SDL_BindGPUGraphicsPipeline(rp, if (cell_decor.nplaques != 0) g.pipeline_over else g.pipeline);
+ // 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);
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) {
@@ -8715,6 +8720,34 @@ fn buildDecor(g: *Gui, gpa: std.mem.Allocator, surface: *const pardes.Surface, g
} else null;
try addLift(g, gpa, lift_style, ceilings, box, rim_edge, bodies, grip.lift, track, win_w, win_h);
}
+ // G7: a pane on the move stands off the page, rising as it sets
+ // out and settling as it lands, its shadow on the other panes'
+ // bodies only (never a tag, a grip or its own text), moving
+ // with it.
+ if (track) |active| if (active.phase != .closing and (active.effect == .slide or active.effect == .zoom or active.effect == .vertical)) {
+ const steps: f32 = @floatFromInt(@max(1, active.frames() -| 1));
+ const u = std.math.clamp(@as(f32, @floatFromInt(active.frame)) / steps, 0, 1);
+ const lift = @sin(std.math.pi * u);
+ var box: [4]f32 = .{ std.math.inf(f32), std.math.inf(f32), -std.math.inf(f32), -std.math.inf(f32) };
+ var others: [pardes.MAX_PANES][4]f32 = undefined;
+ var nothers: usize = 0;
+ for (placed) |part| {
+ if (part.kind != .tag and part.kind != .rail and part.kind != .body and part.kind != .grip) continue;
+ const r = bodyPixels(part, layout);
+ if (part.owner == active.pane) {
+ box = .{ @min(box[0], r[0]), @min(box[1], r[1]), @max(box[2], r[2]), @max(box[3], r[3]) };
+ } else if ((part.kind == .body or part.kind == .rail) and nothers < others.len) {
+ others[nothers] = r;
+ nothers += 1;
+ }
+ }
+ if (box[2] > box[0] and lift > 0.01) {
+ const h: f32 = @floatFromInt(g.cell_h);
+ const first = g.decor.items.len;
+ try addSoft(g, gpa, box, Shadow.sigma * h * lift, Shadow.side * h * lift, Shadow.drop * h * lift, @min(Shadow.strength * lift, ceilings.shadow), .{ 0, 0, 0 }, track, win_w, win_h);
+ try clipToBodies(g, gpa, first, others[0..nothers]);
+ }
+ };
}
// A notice chip is tagline hung over the body's top rows, so it is
// ruled off from the text beneath it the way a tag is: the same
@@ -9287,6 +9320,50 @@ test "decor is read from the regions, each pane's in its own group" {
try std.testing.expectEqual(@as(u32, 0), groups.items[groups.ink].decor_count);
}
+test "a pane on the move casts a shadow on the other panes' bodies alone, most at mid-way" {
+ const gpa = std.testing.allocator;
+ const core = try pardes.Pardes.init(gpa, .{ .cols = 80, .rows = 24 });
+ defer core.deinit();
+ _ = try core.newShell(1, "");
+ try std.testing.expect(pardes.layout.splitColumn(core, 0, 1, false));
+ try std.testing.expect(core.executeBuiltinLine(0, "Theme acme"));
+ for (0..40) |_| core.update(.tick);
+ pardes.test_api.sync(core);
+ const s = try core.render(core.scratch.allocator());
+ var g: Gui = undefined;
+ g.decor = .empty;
+ defer g.decor.deinit(gpa);
+ g.cell_w = 10;
+ g.cell_h = 20;
+ g.tagline_width = 6;
+ g.tagline_height = 12;
+ const layout = fixedCellLayout(&g);
+ const serial = core.panes[1].?.serial;
+ var strengths: [3]f32 = @splat(0);
+ for ([_]u16{ 0, 6, 12 }, 0..) |frame, i| {
+ g.decor.clearRetainingCapacity();
+ const moving: pardes.animation.Track = .{ .pane = 1, .serial = serial, .phase = .moving, .effect = .slide, .frame = frame, .frame_count = 13, .from = .{ .x = 0, .y = 1, .w = 40, .h = 23 }, .to = .{ .x = 40, .y = 1, .w = 40, .h = 23 } };
+ var groups = makeGroups(&.{moving}, true);
+ if (!moving.active()) groups = makeGroups(&.{}, true);
+ try buildDecor(&g, gpa, s, &groups, layout, 800, 480, false);
+ for (g.decor.items) |item| {
+ if (item.effect & soft_shadow_bit == 0) continue;
+ strengths[i] = @max(strengths[i], item.fr);
+ // Clipped to pane 0's body or rail, never pane 1's, never a tag.
+ try std.testing.expect(item.clip_x1 >= item.clip_x0);
+ for (s.regionList()) |region| {
+ if (region.kind != .tag and region.kind != .grip and !(region.owner == 1 and (region.kind == .body or region.kind == .rail))) continue;
+ const r = bodyPixels(region, layout);
+ try std.testing.expect(item.clip_x1 <= r[0] or item.clip_x0 >= r[2] or item.clip_y1 <= r[1] or item.clip_y0 >= r[3]);
+ }
+ }
+ }
+ // Rising as it sets out, gone as it lands.
+ try std.testing.expectEqual(@as(f32, 0), strengths[0]);
+ try std.testing.expect(strengths[1] > 0.05);
+ try std.testing.expectEqual(@as(f32, 0), strengths[2]);
+}
+
/// What a bundled post pass leaves alone (Post.Frame.spare), in window
/// pixels: every tag with its grip, the column and workspace tags, and the
/// notices, the chrome the focus indicators live in. A grip, the gap after