summaryrefslogtreecommitdiff
path: root/example/layouts/manual.lua
blob: 1ba2ebd05d3528f92955216213ad2da4fc29e061 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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,
})