Chaining modifiers
Combine modifiers to build layout variations. Start with a base layout and layer on transformations.
The chain function
Modifiers.chain applies modifiers in sequence:
local Modifiers = require("layouts.modifiers")local Magnifier = require("layouts.modifiers.magnifier")local Mirror = require("layouts.modifiers.mirror")local Tall = require("layouts.tall")
local myLayout = Modifiers.chain(Tall, { Mirror.horizontal(), Magnifier.create({ ratio = 1.3 }),})Execution order
Modifiers execute in array order:
- Base layout computes geometries
- First modifier transforms the result
- Second modifier transforms that output
- And so on…
-- Order: Tall -> Mirror -> MagnifierModifiers.chain(Tall, { Mirror.horizontal(), -- First: flip positions Magnifier.create(), -- Second: enlarge focused})
-- Different order: Tall -> Magnifier -> MirrorModifiers.chain(Tall, { Magnifier.create(), -- First: enlarge focused Mirror.horizontal(), -- Second: flip (including enlarged window)})Order produces different results. In the first example, positions flip before magnification. In the second, magnification happens first, then the enlarged window flips with everything else.
Naming chained layouts
Chained layouts auto-generate names from modifier metadata:
local layout = Modifiers.chain(Tall, { Mirror.horizontal(), Magnifier.create(),})-- layout.name = "tall+mirror-h+magnify"-- layout.displayName = "Tall + Mirror H + Magnify"Override with explicit names:
local layout = Modifiers.chain(Tall, { Mirror.horizontal(), Magnifier.create(),}, { name = "tall-right-mag", displayName = "Tall Right + Magnify",})Preserving actions
Chained layouts don’t inherit base layout actions by default. To preserve
them, pass preserveActions = true:
local layout = Modifiers.chain(Tall, { Mirror.horizontal(),}, { preserveActions = true,})preserveActions is only controlled by the options table.
Examples
Mirrored magnified tall
Master on the right with focused window enlarged:
local Modifiers = require("layouts.modifiers")local Magnifier = require("layouts.modifiers.magnifier")local Mirror = require("layouts.modifiers.mirror")local Tall = require("layouts.tall")
local tallRightMag = Modifiers.chain(Tall, { Mirror.horizontal(), Magnifier.create({ ratio = 1.4 }),}, { name = "tall-right-mag", displayName = "Tall (Right) + Magnify",})
spoon.Shoji:configure({ layouts = { tallRightMag }, enabled_layouts = { "tall", "tall-right-mag" }, retile_on_focus = true, -- Update magnifier on focus change})BSP with custom gaps
BSP with tighter spacing than global defaults:
local Modifiers = require("layouts.modifiers")local Gaps = require("layouts.modifiers.gaps")local BSP = require("layouts.bsp")
local tightBSP = Modifiers.chain(BSP, { Gaps.add({ inner = 2, outer = 4 }),}, { name = "bsp-tight", displayName = "BSP (Tight)", preserveActions = true, -- Keep BSP resize/rotate actions})Centered layout
Scale and center at 70% of screen size:
local Modifiers = require("layouts.modifiers")local Centered = require("layouts.modifiers.centered")local Tall = require("layouts.tall")
local centeredTall = Modifiers.chain(Tall, { Centered.ratio(0.7),}, { name = "tall-centered", displayName = "Tall (Centered)",})Error handling
chain returns nil and an error for invalid input:
-- Empty modifier listlocal layout, err = Modifiers.chain(Tall, {})-- err = "at least one modifier required"
-- Invalid modifierlocal layout, err = Modifiers.chain(Tall, { "not a function" })-- err = "modifier at index 1 must be a function"Debugging
Add a pass-through modifier to inspect intermediate geometries:
local function debugModifier(geometries, params) print("Geometries after previous modifier:") for id, frame in pairs(geometries) do print(string.format(" %d: x=%d y=%d w=%d h=%d", id, frame.x, frame.y, frame.w, frame.h)) end return geometries -- Pass through unchangedend
local debuggedLayout = Modifiers.chain(Tall, { Mirror.horizontal(), debugModifier, Magnifier.create(),})