Something went wrong. Try again.
window manager for the river wayland compositor
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366// zlinter-disable file_naming - pure-function module keeps PascalCase name.// SPDX-FileCopyrightText: © 2026 mtmn//// SPDX-License-Identifier: BSD-3-Clause
const std = @import("std");
pub const Location = enum { top, right, bottom, left,};
pub const Attach = enum { top, bottom,};
pub const Rect = struct { x: i32, y: i32, width: i32, height: i32,};
pub const Placement = Rect;
/// Split the output rectangle between a main area and a secondary area./// Windows divide their area evenly, the last window takes any remainder./// Pure and total: safe for any count, including zero.pub fn compute( placements: []Placement, output: Rect, main_count: u32, ratio: f32, location: Location,) void { const count = placements.len; if (count == 0) return; if (output.width <= 0 or output.height <= 0) { for (placements) |*placement| { placement.* = .{ .x = output.x, .y = output.y, .width = 0, .height = 0, }; } return; }
const clamped_ratio = if (std.math.isFinite(ratio)) std.math.clamp(ratio, 0.1, 0.9) else 0.5; const mains: usize = @min(@as(usize, @intCast(main_count)), count);
const vertical = location == .left or location == .right;
// Without secondary windows the main area spans the whole output, // so a lone window (or exactly main_count windows) always fills it. if (count <= mains) { split(placements, output, vertical); return; } const output_width: f32 = @floatFromInt(@max(output.width, 0)); const output_height: f32 = @floatFromInt(@max(output.height, 0));
const main_width: i32 = @min( @max(@as(i32, @intFromFloat(output_width * clamped_ratio)), 0), @max(output.width, 0), ); const main_height: i32 = @min( @max(@as(i32, @intFromFloat(output_height * clamped_ratio)), 0), @max(output.height, 0), );
const main_rect, const secondary_rect = switch (location) { .left => blk: { break :blk .{ Rect{ .x = output.x, .y = output.y, .width = main_width, .height = output.height }, Rect{ .x = output.x + main_width, .y = output.y, .width = output.width - main_width, .height = output.height, }, }; }, .right => blk: { break :blk .{ Rect{ .x = output.x + output.width - main_width, .y = output.y, .width = main_width, .height = output.height, }, Rect{ .x = output.x, .y = output.y, .width = output.width - main_width, .height = output.height, }, }; }, .top => blk: { break :blk .{ Rect{ .x = output.x, .y = output.y, .width = output.width, .height = main_height }, Rect{ .x = output.x, .y = output.y + main_height, .width = output.width, .height = output.height - main_height, }, }; }, .bottom => blk: { break :blk .{ Rect{ .x = output.x, .y = output.y + output.height - main_height, .width = output.width, .height = main_height, }, Rect{ .x = output.x, .y = output.y, .width = output.width, .height = output.height - main_height, }, }; }, };
split(placements[0..mains], main_rect, vertical); split(placements[mains..], secondary_rect, vertical);}
fn split(placements: []Placement, area: Rect, vertical: bool) void { if (placements.len == 0) return; if (vertical) { const height_each = @divFloor(area.height, @as(i32, @intCast(placements.len))); for (placements[0 .. placements.len - 1], 0..) |*placement, i| { placement.* = .{ .x = area.x, .y = area.y + @as(i32, @intCast(i)) * height_each, .width = area.width, .height = height_each, }; } const last = &placements[placements.len - 1]; last.* = .{ .x = area.x, .y = area.y + @as(i32, @intCast(placements.len - 1)) * height_each, .width = area.width, .height = area.height - height_each * @as(i32, @intCast(placements.len - 1)), }; } else { const width_each = @divFloor(area.width, @as(i32, @intCast(placements.len))); for (placements[0 .. placements.len - 1], 0..) |*placement, i| { placement.* = .{ .x = area.x + @as(i32, @intCast(i)) * width_each, .y = area.y, .width = width_each, .height = area.height, }; } const last = &placements[placements.len - 1]; last.* = .{ .x = area.x + @as(i32, @intCast(placements.len - 1)) * width_each, .y = area.y, .width = area.width - width_each * @as(i32, @intCast(placements.len - 1)), .height = area.height, }; }}
test "single window fills output" { var placements: [1]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1920, .height = 1080 }, 1, 0.6, .left); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 1920, .height = 1080 }, placements[0], );}
test "windows equal to main count fill output" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 800 }, 2, 0.6, .left); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 1000, .height = 400 }, placements[0], ); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 400, .width = 1000, .height = 400 }, placements[1], );}
test "single window fills output with top location" { var placements: [1]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1920, .height = 1080 }, 1, 0.6, .top); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 1920, .height = 1080 }, placements[0], );}
test "no windows is a no-op" { compute(&.{}, .{ .x = 0, .y = 0, .width = 1920, .height = 1080 }, 1, 0.6, .left);}
test "two windows split main and secondary" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 800 }, 1, 0.6, .left); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 600, .height = 800 }, placements[0], ); try std.testing.expectEqualDeep( Placement{ .x = 600, .y = 0, .width = 400, .height = 800 }, placements[1], );}
test "right location mirrors areas" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 800 }, 1, 0.6, .right); try std.testing.expectEqualDeep( Placement{ .x = 400, .y = 0, .width = 600, .height = 800 }, placements[0], ); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 400, .height = 800 }, placements[1], );}
test "main count divides main area evenly" { var placements: [3]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 900 }, 2, 0.5, .left); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 500, .height = 450 }, placements[0], ); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 450, .width = 500, .height = 450 }, placements[1], ); try std.testing.expectEqualDeep( Placement{ .x = 500, .y = 0, .width = 500, .height = 900 }, placements[2], );}
test "ratio clamps to sane bounds" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 800 }, 1, 99.0, .left); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 900, .height = 800 }, placements[0], ); try std.testing.expectEqualDeep( Placement{ .x = 900, .y = 0, .width = 100, .height = 800 }, placements[1], );}
test "invalid dimensions produce non-negative placements" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = -100, .height = -50 }, 1, 0.6, .left); for (placements) |placement| { try std.testing.expect(placement.width >= 0); try std.testing.expect(placement.height >= 0); }}
test "non-finite ratio remains safe" { var placements: [1]Placement = undefined; compute( &placements, .{ .x = 0, .y = 0, .width = 1000, .height = 800 }, 1, std.math.nan(f32), .left, ); try std.testing.expect(placements[0].width >= 0); try std.testing.expect(placements[0].height >= 0);}
// Gapless tiling: neighbours share edges and the union equals the area.// Odd sizes and clamped ratios exercise the remainder paths.test "odd width leaves no vertical seam" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1001, .height = 800 }, 1, 0.5, .left); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 500, .height = 800 }, placements[0], ); try std.testing.expectEqualDeep( Placement{ .x = 500, .y = 0, .width = 501, .height = 800 }, placements[1], ); try std.testing.expectEqual(placements[1].x, placements[0].x + placements[0].width); try std.testing.expectEqual(1001, placements[0].width + placements[1].width);}
test "odd width mirrors with no seam on right" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1001, .height = 800 }, 1, 0.5, .right); try std.testing.expectEqual(placements[0].x, placements[1].x + placements[1].width); try std.testing.expectEqual(1001, placements[0].width + placements[1].width); try std.testing.expectEqual(800, placements[0].height); try std.testing.expectEqual(800, placements[1].height);}
test "odd height leaves no horizontal seam on top" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 801 }, 1, 0.5, .top); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 1000, .height = 400 }, placements[0], ); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 400, .width = 1000, .height = 401 }, placements[1], ); try std.testing.expectEqual(placements[1].y, placements[0].y + placements[0].height); try std.testing.expectEqual(801, placements[0].height + placements[1].height);}
test "secondary stack leaves no vertical gaps" { var placements: [3]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1000, .height = 901 }, 1, 0.5, .left); try std.testing.expectEqualDeep( Placement{ .x = 0, .y = 0, .width = 500, .height = 901 }, placements[0], ); try std.testing.expectEqualDeep( Placement{ .x = 500, .y = 0, .width = 500, .height = 450 }, placements[1], ); try std.testing.expectEqualDeep( Placement{ .x = 500, .y = 450, .width = 500, .height = 451 }, placements[2], ); try std.testing.expectEqual(placements[2].y, placements[1].y + placements[1].height); try std.testing.expectEqual(901, placements[1].height + placements[2].height);}
test "both columns stack with no gaps" { var placements: [4]Placement = undefined; compute(&placements, .{ .x = 10, .y = 20, .width = 1000, .height = 901 }, 2, 0.5, .left); try std.testing.expectEqual(placements[1].y, placements[0].y + placements[0].height); try std.testing.expectEqual(placements[3].y, placements[2].y + placements[2].height); try std.testing.expectEqual(placements[2].x, placements[0].x + placements[0].width); try std.testing.expectEqual(1010, placements[3].x + placements[3].width); try std.testing.expectEqual(921, placements[1].y + placements[1].height); try std.testing.expectEqual(921, placements[3].y + placements[3].height);}
test "clamped ratio still covers output" { var placements: [2]Placement = undefined; compute(&placements, .{ .x = 0, .y = 0, .width = 1001, .height = 800 }, 1, 99.0, .left); try std.testing.expectEqual(placements[1].x, placements[0].x + placements[0].width); try std.testing.expectEqual(1001, placements[0].width + placements[1].width);}