Skip to content

Layouts

Pinnacle's layout system dynamically tiles windows according to a tree structure. More specifically, we use Taffy to compute layouts, so if you're familiar with CSS Flexbox, you have a good idea of how the layout system works.

General architecture

When Pinnacle wants to layout a set of windows, it sends a request to your config. The request contains information like the amount of windows being laid out, what tags are currently active, and the output the layout is occurring on. In response to the layout request, your config generates a layout tree that is then sent to the compositor. Pinnacle processes that tree and computes a new layout. Finally, windows are assigned the resulting geometries and are updated.

Managing layouts

Of course, you need a way to get the config to respond to layout requests. You can do this through the manage function. You must provide a function that takes layout arguments as input and returns a layout response containing a layout tree.

lua
require("pinnacle.layout").manage(function(layout_args)
    -- Calculate and return a layout response
end)

Builtin layouts

For now, we'll use the layouts built into the Lua and Rust APIs, then discuss custom layouts later.

Pinnacle abstracts layout generation through the layout generator interface. Layout generators are structs/tables with a layout methods that takes in a window count and returns the root of a layout tree through a layout node.

The API provides a set of builtin layout generators that should suffice for most people. They cover most of the builtin layouts that Awesome has; specifically, the master stack, spiral, dwindle, corner, and fair layouts.

Let's manage layouts by generating a master stack layout.

lua
require("pinnacle.layout").manage(function(layout_args)
    local master_stack = require("pinnacle.layout").builtin.master_stack()
    local root_node = master_stack:layout(layout_args.window_count)
    -- TODO
end)

TIP

Builtin generators may have fields that change layout generation. Most of them have gaps fields that control window gaps, and generators like the master stack generator allow you to change other aspects, like which side of the screen the master side is on.

The identifier

The response additionally includes an identifier used to identify the layout tree. This identifier should be the same when two submitted layout trees are considered "the same". For example, trees generated by a certain generator should be given the same identifier. This is used to track differences between submitted trees for resizing purposes.

For our running example, we may opt to use the current tag's id as the identifier. If we resize our tiles (discussed later), those sizes will be remembered per tag in this case. Note that we use only the first tag for simplicity.

lua
require("pinnacle.layout").manage(function(layout_args)
    local master_stack = require("pinnacle.layout").builtin.master_stack()
    local root_node = master_stack:layout(layout_args.window_count)
    local tree_id = layout_args.tags[1] and layout_args.tags[1].id or 0

    return {
        root_node = root_node,
        tree_id = tree_id,
    }
end)

There we have it! We've set up our config to make all tiled windows lay out according to the master stack layout.

If you're comfortable using just the master stack layout, this will be sufficient. However, what if you want to have multiple layouts and be able to cycle between them? That is the role of the Cycle layout generator.

The Cycle layout generator

The Cycle layout generator is unique: instead of computing its own layout, it delegates to a list of provided layout generators based on which tag is currently active. As the name suggests, it provides actions that allow you to cycle through those layouts, tracking the current layout per tag.

Let's manage layouts using this generator.

lua
local cycle = require("pinnacle.layout").builtin.cycle({
    require("pinnacle.layout").builtin.master_stack(),
    require("pinnacle.layout").builtin.dwindle(),
    require("pinnacle.layout").builtin.fair(),
    -- Possibly more layouts
})

require("pinnacle.layout").manage(function(layout_args)
    local first_tag = layout_args.tags[1]
    if not first_tag then
        ---@type pinnacle.layout.LayoutResponse
        return {
            root_node = {},
            tree_id = 0,
        }
    end
    cycle.current_tag = first_tag
    local root_node = cycle:layout(layout_args.window_count)
    local tree_id = cycle:current_tree_id()
    ---@type pinnacle.layout.LayoutResponse
    return {
        root_node = root_node,
        tree_id = tree_id,
    }
end)

Observe:

  1. We need to set the current tag so that Cycle can use the correct generator.
  2. We use Cycle::current_tree_id to get a unique identifier for the current tree.

To cycle the current layout, call Cycle::cycle_layout_forward or Cycle::cycle_layout_backward.