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.
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.
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.
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.
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:
- We need to set the current tag so that
Cycle
can use the correct generator. - 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
.