Layout Overview
The layout engine models a tiling window manager as an infinite horizontal canvas that the user scrolls through. Columns of stacked windows extend left and right without bound; a sliding camera determines which slice of that canvas is visible on screen. The entire layout module (src/layout/) is pure Rust with zero Win32 dependencies, making every operation fully unit-testable on any platform.
Two-layer model
The system splits cleanly into a virtual layer and an actual layer. The virtual layer describes what exists on the canvas — columns, their pixel widths, and a camera offset — but stores no absolute x-coordinates for any window. The actual layer is the projected output: concrete pixel rectangles that Windows OS renders. This separation keeps mutations simple (they operate on the virtual canvas, never on screen coordinates) and makes projection a single deterministic function.
classDiagram
class VirtualLayout {
+Vec~Column~ columns
+i32 viewport_offset
+find_window(id) Option~(usize, usize)~
+window_count() usize
}
class Column {
+i32 width_px
+Vec~WindowId~ rows
+new(width_px, window) Column
+is_valid_width() bool
}
class ActualLayout {
+Vec~ActualEntry~ entries
+find(id) Option~ActualEntry~
}
class ActualEntry {
+WindowId window_id
+Rect rect
}
class AppliedLayout {
+VirtualLayout virtual_layout
+ActualLayout actual_layout
}
class MonitorInfo {
+Rect work_area
}
class Padding {
+i32 window_gap
+i32 up
+i32 down
}
VirtualLayout "1" *-- "*" Column : contains
VirtualLayout --> ActualLayout : projected by project()
ActualLayout "1" *-- "*" ActualEntry : contains
AppliedLayout --> VirtualLayout : wraps
AppliedLayout --> ActualLayout : wraps
VirtualLayout holds the ordered columns vector and a viewport_offset — the camera position measured in pixels from the canvas origin. Column stores only a width_px and a rows list; it carries no x-position. ActualLayout is produced by the projection step and contains one ActualEntry per window, each with a concrete Rect in screen coordinates. AppliedLayout bundles both the new virtual and actual layouts together — the animation layer diffs every window’s target rect against its real on-screen position to decide what needs tweening.
The camera model
viewport_offset on VirtualLayout acts as a camera sliding along the infinite canvas. A value of 0 means the camera is flush with the left edge of the first column. Increasing it scrolls the viewport rightward. Many operations — scrolling, focusing an off-screen window, swapping columns — are implemented by adjusting this offset rather than moving individual windows. The projection step then computes screen coordinates from the offset plus the column geometry.
graph LR
subgraph "Infinite Canvas"
direction LR
C1["Column 0"]
C2["Column 1"]
C3["Column 2"]
C4["Column 3"]
C5["Column 4"]
end
subgraph Viewport
direction LR
V1["visible"]
end
PL["Parked Left"] -.-> C1
C2 --> V1
C3 --> V1
PR["Parked Right"] -.-> C4
C5 -.-> PR
style PL fill:#f9f,stroke:#333,stroke-dasharray: 5 5
style PR fill:#f9f,stroke:#333,stroke-dasharray: 5 5
style Viewport fill:#bbf,stroke:#333,stroke-width:2px
Columns outside the viewport are parked at deterministic off-screen positions — one column-width beyond the nearest viewport edge. Windows OS does not gracefully ignore windows placed at extreme off-screen coordinates, so parking them nearby keeps the window manager well-behaved and enables smooth scroll-in/out animations. See Projection for the full parking algorithm.
Why position is implicit
Column x-positions are never stored. Instead they are computed on the fly via prefix-sum of widths: column 0 starts at window_gap, column i starts at the right edge of column i-1 plus window_gap. This design has a direct practical benefit — swapping two columns is a simple Vec::swap with zero coordinate arithmetic. Adding or removing a column shifts all downstream windows automatically because the prefix-sum recomputes from scratch on every projection.
Pixel widths, not eighths
Column widths are stored directly as pixel values (width_px). An earlier revision used eighths of the configured column_width to stay resolution-independent. That quantization had a subtle bug: expand_column computed the correct target width (column_width + window_gap), but converting back to eighths rounded the gap away (it was smaller than one eighth), so each expand step grew by exactly column_width instead of column_width + window_gap. Pixel widths make the gap observable at every step and let expand/shrink advance by the true column_shift. See Design Decisions for the full trade-off analysis.
Padding at projection time
Padding — the uniform window_gap between windows and screen edges, plus optional up/down top/bottom margins — is applied exclusively during the projection step. Neither VirtualLayout nor Column stores padding. This keeps the virtual canvas model clean: columns are pure containers of windows with widths, and the spatial realities of screen edges, gaps, and margins are deferred to the one function that converts canvas geometry to screen coordinates.
What lives where
The src/layout/ module contains only the pure math: type definitions, mutation functions, and the projection function. There is no orchestrator here. ScrollingSpace (in src/workspace/scrolling_space.rs) owns the current VirtualLayout, calls mutation functions to produce new virtual layouts, passes them through projection, and wraps the result in an AppliedLayout for the animation layer. This separation means the layout math can be tested in isolation from any workspace or Win32 state.
The full data-flow pipeline — mutation, projection, animation — is described in Pipeline. The mutation catalog and their algorithms are covered in Mutations.