From 61fd784246af744b8311949a26946a4c19ed4e2a Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Mon, 19 Jan 2026 14:29:08 -0600 Subject: [PATCH] Implement initial tiling layout. For now, it's hardcoded to be a right-primary/stack layout (similar to dwm but with the primary on the right) with the primary window getting 55% of the width of the screen. --- src/Window.zig | 43 +++++++++++++++--------------- src/WindowManager.zig | 61 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 0bcefae..467e15c 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -7,6 +7,7 @@ const Window = @This(); context: *Context, window_v1: *river.WindowV1, +node_v1: *river.NodeV1, width: u31 = 0, height: u31 = 0, @@ -19,12 +20,12 @@ new_x: i32 = 0, new_y: i32 = 0, link: wl.list.Link, -is_head: bool = false, pub fn init(window: *Window, context: *Context, river_window_v1: *river.WindowV1) void { window.* = .{ .context = context, .window_v1 = river_window_v1, + .node_v1 = river_window_v1.getNode() catch @panic("failed to get node"), .link = undefined, // Handled by the wl.list }; @@ -36,6 +37,8 @@ fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event, switch (event) { .closed => { river_window_v1.destroy(); + window.node_v1.destroy(); + window.context.wm.window_count -= 1; { var it = window.context.wm.seats.iterator(.forward); while (it.next()) |seat| { @@ -63,32 +66,30 @@ fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event, } pub fn manage(window: *Window) void { - if (window.context.wm.outputs.length() == 0) { - window.new_width = 0; - window.new_height = 0; - window.new_x = 0; - window.new_y = 0; - } + // Layout has been pre-computed by WindowManager.calculatePrimaryStackLayout() + // Apply the computed dimensions + if (window.new_width > 0 and window.new_height > 0) { + window.window_v1.proposeDimensions(window.new_width, window.new_height); - // TODO: Support multiple primaries - // TODO: Is this a valid way to check for the window's index? + // Use server-side decoration (no client-drawn title bars) + window.window_v1.useSsd(); - // TODO: Remove this -- just fullscreen for now - if (window.width != window.context.wm.outputs.first().?.width or - window.height != window.context.wm.outputs.first().?.height) - { - window.width = @intCast(window.context.wm.outputs.first().?.width); - window.height = @intCast(window.context.wm.outputs.first().?.height); - log.debug("setting window width={d} and height={d} {}", .{ window.width, window.height, window.is_head }); + // Inform the window about its tiled/fullscreen state + if (window.context.wm.window_count == 1) { + // Single window is fullscreen + window.window_v1.setTiled(.{ .top = false, .bottom = false, .left = false, .right = false }); + window.window_v1.informFullscreen(); + } else { + // Tiled windows - set tiled edges + window.window_v1.informNotFullscreen(); + window.window_v1.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true }); + } } - window.window_v1.proposeDimensions(window.width, window.height); - // window.window_v1.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true }); - window.window_v1.informFullscreen(); - // window.window_v1.informMaximized(); } pub fn render(window: *Window) void { - _ = window; // TODO ??? + // Set the window position using the pre-computed layout + window.node_v1.setPosition(window.new_x, window.new_y); } const std = @import("std"); diff --git a/src/WindowManager.zig b/src/WindowManager.zig index 516a2f6..ba67e54 100644 --- a/src/WindowManager.zig +++ b/src/WindowManager.zig @@ -4,6 +4,8 @@ const WindowManager = @This(); +const PRIMARY_RATIO: f32 = 0.55; + context: *Context, window_manager_v1: *river.WindowManagerV1, @@ -31,6 +33,60 @@ pub fn init(wm: *WindowManager, context: *Context, window_manager_v1: *river.Win wm.window_manager_v1.setListener(*WindowManager, windowManagerV1Listener, wm); } +/// Calculate primary/stack layout positions for all windows. +/// - Single window: fullscreen +/// - Multiple windows: stack (45% left, vertically tiled), primary (55% right) +pub fn calculatePrimaryStackLayout(wm: *WindowManager) void { + const count = wm.window_count; + if (count == 0) return; + + const output = wm.outputs.first() orelse return; + // Output dimensions come as i32 from the protocol, convert to u31 for window dimensions + const output_width: u31 = @intCast(output.width); + const output_height: u31 = @intCast(output.height); + const output_x = output.x; + const output_y = output.y; + + var index: u8 = 0; + var it = wm.windows.iterator(.forward); + while (it.next()) |window| { + if (count == 1) { + // Single window: fullscreen + window.new_x = output_x; + window.new_y = output_y; + window.new_width = output_width; + window.new_height = output_height; + } else { + // Multiple windows: primary/stack layout + const primary_width: u31 = @intFromFloat(@as(f32, @floatFromInt(output_width)) * PRIMARY_RATIO); + const stack_width: u31 = output_width - primary_width; + const stack_count = count - 1; + const stack_height: u31 = @divFloor(output_height, stack_count); + + if (index == 0) { + // Primary window (first window) - right side + window.new_x = output_x + @as(i32, stack_width); + window.new_y = output_y; + window.new_width = primary_width; + window.new_height = output_height; + } else { + // Stack window - left side + const stack_index = index - 1; + window.new_x = output_x; + window.new_y = output_y + @as(i32, stack_index) * @as(i32, stack_height); + window.new_width = stack_width; + // Last stack window gets remaining height to avoid gaps from integer division + if (index == count - 1) { + window.new_height = output_height - stack_index * stack_height; + } else { + window.new_height = stack_height; + } + } + } + index += 1; + } +} + fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event, wm: *WindowManager) void { assert(wm.window_manager_v1 == window_manager_v1); const context = wm.context; @@ -56,6 +112,8 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv output.manage(); } } + // Calculate layout before managing windows + wm.calculatePrimaryStackLayout(); { var it = wm.windows.iterator(.forward); while (it.next()) |window| { @@ -103,9 +161,6 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv window.init(context, ev.id); wm.windows.append(window); wm.window_count += 1; - if (wm.window_count == 1) { - window.is_head = true; - } log.debug("window_count = {d}", .{wm.window_count}); }, else => |ev| {