a73x

70396bf0

Add SurfaceState struct for visibility tracking

a73x   2026-04-16 10:44

Single source of truth for whether the surface is currently mapped,
configured, and not suspended. Not yet wired into Window — that happens
in a follow-up.

diff --git a/src/wayland.zig b/src/wayland.zig
index 31366aa..c136a8c 100644
--- a/src/wayland.zig
+++ b/src/wayland.zig
@@ -18,6 +18,18 @@ pub const Output = struct {
    pending_scale: i32 = 1,
};

pub const SurfaceState = struct {
    configured: bool = false,
    suspended: bool = false,
    tracker: *ScaleTracker,

    pub fn visible(self: *const SurfaceState) bool {
        return self.configured
            and !self.suspended
            and self.tracker.enteredCount() > 0;
    }
};

pub const KeyboardEvent = struct {
    keysym: u32,
    modifiers: Modifiers,
@@ -1217,3 +1229,29 @@ test "Window.bufferScale reflects ScaleTracker entered outputs" {
    tracker.leaveOutput(2);
    try std.testing.expectEqual(@as(i32, 1), tracker.bufferScale());
}

test "SurfaceState.visible requires configured && !suspended && enteredCount > 0" {
    var tracker = ScaleTracker.init(std.testing.allocator);
    defer tracker.deinit();
    try tracker.addOutput(1);

    var state = SurfaceState{ .tracker = &tracker };

    // Unconfigured → not visible
    try std.testing.expect(!state.visible());

    state.configured = true;
    // No entered outputs yet → not visible
    try std.testing.expect(!state.visible());

    try tracker.enterOutput(1);
    // Configured + entered + not suspended → visible
    try std.testing.expect(state.visible());

    state.suspended = true;
    try std.testing.expect(!state.visible());

    state.suspended = false;
    tracker.leaveOutput(1);
    try std.testing.expect(!state.visible());
}