summaryrefslogtreecommitdiff
path: root/example
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2026-05-06 13:56:12 +0100
committerGitHub <noreply@github.com>2026-05-06 13:56:12 +0100
commit1681bea42dd2f11ba3fe6df05560d0b231de3c76 (patch)
tree899103dbddf675a01758c7cdd379224c9c81a7fb /example
parent5f1350f522d3fe3af78cbe30a98311763b3509d5 (diff)
config/lua: add simple layout API (#14258)
Adds a simple layout lua api
Diffstat (limited to 'example')
-rw-r--r--example/layouts/columns.lua12
-rw-r--r--example/layouts/grid.lua14
-rw-r--r--example/layouts/manual.lua144
-rw-r--r--example/layouts/spiral.lua55
4 files changed, 225 insertions, 0 deletions
diff --git a/example/layouts/columns.lua b/example/layouts/columns.lua
new file mode 100644
index 00000000..dc907cd4
--- /dev/null
+++ b/example/layouts/columns.lua
@@ -0,0 +1,12 @@
+hl.layout.register("columns", {
+ recalculate = function(ctx)
+ local n = #ctx.targets
+ if n == 0 then
+ return
+ end
+
+ for i, target in ipairs(ctx.targets) do
+ target:place(ctx:column(i, n))
+ end
+ end,
+})
diff --git a/example/layouts/grid.lua b/example/layouts/grid.lua
new file mode 100644
index 00000000..10a0cab1
--- /dev/null
+++ b/example/layouts/grid.lua
@@ -0,0 +1,14 @@
+hl.layout.register("grid", {
+ recalculate = function(ctx)
+ local n = #ctx.targets
+ if n == 0 then
+ return
+ end
+
+ local cols = math.ceil(math.sqrt(n))
+
+ for i, target in ipairs(ctx.targets) do
+ target:place(ctx:grid_cell(i, cols))
+ end
+ end,
+})
diff --git a/example/layouts/manual.lua b/example/layouts/manual.lua
new file mode 100644
index 00000000..1ba2ebd0
--- /dev/null
+++ b/example/layouts/manual.lua
@@ -0,0 +1,144 @@
+local state = {
+ order = {},
+ split = {},
+ default_split = "h",
+}
+
+local function target_id(target)
+ local window = target.window
+ return window and tostring(window.stable_id) or tostring(target.index)
+end
+
+local function active_id(ctx)
+ for _, target in ipairs(ctx.targets) do
+ local window = target.window
+ if window and window.active then
+ return target_id(target)
+ end
+ end
+
+ return state.order[#state.order]
+end
+
+local function index_of(tbl, value)
+ for i, v in ipairs(tbl) do
+ if v == value then
+ return i
+ end
+ end
+end
+
+local function sync_order(ctx)
+ local present = {}
+ local targets = {}
+
+ for _, target in ipairs(ctx.targets) do
+ local id = target_id(target)
+ present[id] = true
+ targets[id] = target
+ end
+
+ local old_order = state.order
+ state.order = {}
+
+ for _, id in ipairs(old_order) do
+ if present[id] then
+ table.insert(state.order, id)
+ else
+ state.split[id] = nil
+ end
+ end
+
+ local focused = active_id(ctx)
+ for _, target in ipairs(ctx.targets) do
+ local id = target_id(target)
+ if not index_of(state.order, id) then
+ local after = focused and index_of(state.order, focused)
+ table.insert(state.order, after and (after + 1) or (#state.order + 1), id)
+ end
+ end
+
+ return targets
+end
+
+local function place_chain(ctx, targets, ids, area, i)
+ if i > #ids then
+ return
+ end
+
+ local target = targets[ids[i]]
+ if not target then
+ return
+ end
+
+ if i == #ids then
+ target:place(area)
+ return
+ end
+
+ local split = state.split[ids[i]] or state.default_split
+ if split == "v" then
+ target:place(ctx:split(area, "top", 0.5))
+ place_chain(ctx, targets, ids, ctx:split(area, "bottom", 0.5), i + 1)
+ else
+ target:place(ctx:split(area, "left", 0.5))
+ place_chain(ctx, targets, ids, ctx:split(area, "right", 0.5), i + 1)
+ end
+end
+
+local function move_active(ctx, delta)
+ local id = active_id(ctx)
+ local i = id and index_of(state.order, id)
+ local j = i and (i + delta)
+
+ if not i or j < 1 or j > #state.order then
+ return
+ end
+
+ state.order[i], state.order[j] = state.order[j], state.order[i]
+end
+
+hl.layout.register("manual", {
+ recalculate = function(ctx)
+ local targets = sync_order(ctx)
+ place_chain(ctx, targets, state.order, ctx.area, 1)
+ end,
+
+ layout_msg = function(ctx, msg)
+ local id = active_id(ctx)
+ local command = msg:match("^(%S+)")
+
+ if command == "splith" or command == "h" then
+ if id then
+ state.split[id] = "h"
+ end
+ elseif command == "splitv" or command == "v" then
+ if id then
+ state.split[id] = "v"
+ end
+ elseif command == "splittoggle" or command == "toggle" then
+ if id then
+ state.split[id] = state.split[id] == "v" and "h" or "v"
+ end
+ elseif command == "promote" then
+ local i = id and index_of(state.order, id)
+ if i then
+ table.remove(state.order, i)
+ table.insert(state.order, 1, id)
+ end
+ elseif command == "swapnext" then
+ move_active(ctx, 1)
+ elseif command == "swapprev" then
+ move_active(ctx, -1)
+ elseif command == "rotate" then
+ for k, v in pairs(state.split) do
+ state.split[k] = v == "v" and "h" or "v"
+ end
+ state.default_split = state.default_split == "v" and "h" or "v"
+ else
+ return "manual: expected splith, splitv, splittoggle, promote, swapnext, swapprev, or rotate"
+ end
+
+ return true
+ end,
+})
diff --git a/example/layouts/spiral.lua b/example/layouts/spiral.lua
new file mode 100644
index 00000000..e97f4dbd
--- /dev/null
+++ b/example/layouts/spiral.lua
@@ -0,0 +1,55 @@
+local state = {
+ ratio = 0.58,
+ offset = 0,
+}
+
+local sides = { "left", "top", "right", "bottom" }
+local opposite = {
+ left = "right",
+ right = "left",
+ top = "bottom",
+ bottom = "top",
+}
+
+local function clamp(x, min, max)
+ return math.max(min, math.min(max, x))
+end
+
+hl.layout.register("spiral", {
+ recalculate = function(ctx)
+ local n = #ctx.targets
+ if n == 0 then
+ return
+ end
+
+ local area = ctx.area
+
+ for i, target in ipairs(ctx.targets) do
+ if i == n then
+ target:place(area)
+ else
+ local side = sides[((i - 1 + state.offset) % #sides) + 1]
+ target:place(ctx:split(area, side, state.ratio))
+ area = ctx:split(area, opposite[side], 1.0 - state.ratio)
+ end
+ end
+ end,
+
+ layout_msg = function(ctx, msg)
+ local command, arg = msg:match("^(%S+)%s*(.*)$")
+
+ if command == "ratio" then
+ state.ratio = clamp(tonumber(arg) or state.ratio, 0.1, 0.9)
+ elseif command == "grow" then
+ state.ratio = clamp(state.ratio + 0.05, 0.1, 0.9)
+ elseif command == "shrink" then
+ state.ratio = clamp(state.ratio - 0.05, 0.1, 0.9)
+ elseif command == "rotate" then
+ state.offset = (state.offset + 1) % #sides
+ else
+ return "spiral: expected ratio <0.1..0.9>, grow, shrink, or rotate"
+ end
+
+ return true
+ end,
+})