mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-14 08:01:14 +01:00
Add fair layout
This commit is contained in:
parent
a8ec13d7d2
commit
ab2b3ee13b
4 changed files with 202 additions and 20 deletions
|
@ -91,7 +91,9 @@ require("pinnacle").setup(function(Pinnacle)
|
||||||
local layout_manager = Layout.new_cycling_manager({
|
local layout_manager = Layout.new_cycling_manager({
|
||||||
Layout.builtins.master_stack,
|
Layout.builtins.master_stack,
|
||||||
Layout.builtins.dwindle,
|
Layout.builtins.dwindle,
|
||||||
|
Layout.builtins.spiral,
|
||||||
Layout.builtins.corner,
|
Layout.builtins.corner,
|
||||||
|
Layout.builtins.fair,
|
||||||
})
|
})
|
||||||
|
|
||||||
Layout.set_manager(layout_manager)
|
Layout.set_manager(layout_manager)
|
||||||
|
|
|
@ -42,27 +42,31 @@ end
|
||||||
---@class LayoutGenerator
|
---@class LayoutGenerator
|
||||||
---Generate an array of geometries from the given `LayoutArgs`.
|
---Generate an array of geometries from the given `LayoutArgs`.
|
||||||
---@field layout fun(self: self, args: LayoutArgs): { x: integer, y: integer, width: integer, height: integer }[]
|
---@field layout fun(self: self, args: LayoutArgs): { x: integer, y: integer, width: integer, height: integer }[]
|
||||||
|
---Gaps between windows.
|
||||||
|
---
|
||||||
|
---Generators are free to ignore this, but it is recommended to implement gaps if
|
||||||
|
---it makes sense for the layout.
|
||||||
|
---@field gaps integer | { inner: integer, outer: integer }
|
||||||
|
|
||||||
---@class Builtin.MasterStack : LayoutGenerator
|
---@class Builtin.MasterStack : LayoutGenerator
|
||||||
---@field gaps integer | { inner: integer, outer: integer }
|
|
||||||
---@field master_factor number
|
---@field master_factor number
|
||||||
---@field master_side "left"|"right"|"top"|"bottom"
|
---@field master_side "left"|"right"|"top"|"bottom"
|
||||||
---@field master_count integer
|
---@field master_count integer
|
||||||
|
|
||||||
---@class Builtin.Dwindle : LayoutGenerator
|
---@class Builtin.Dwindle : LayoutGenerator
|
||||||
---@field gaps integer | { inner: integer, outer: integer }
|
|
||||||
---@field split_factors table<integer, number>
|
---@field split_factors table<integer, number>
|
||||||
|
|
||||||
---@class Builtin.Corner : LayoutGenerator
|
---@class Builtin.Corner : LayoutGenerator
|
||||||
---@field gaps integer | { inner: integer, outer: integer }
|
|
||||||
---@field corner_width_factor number
|
---@field corner_width_factor number
|
||||||
---@field corner_height_factor number
|
---@field corner_height_factor number
|
||||||
---@field corner_loc "top_left"|"top_right"|"bottom_left"|"bottom_right"
|
---@field corner_loc "top_left"|"top_right"|"bottom_left"|"bottom_right"
|
||||||
|
|
||||||
---@class Builtin.Spiral : LayoutGenerator
|
---@class Builtin.Spiral : LayoutGenerator
|
||||||
---@field gaps integer | { inner: integer, outer: integer }
|
|
||||||
---@field split_factors table<integer, number>
|
---@field split_factors table<integer, number>
|
||||||
|
|
||||||
|
---@class Builtin.Fair : LayoutGenerator
|
||||||
|
---@field direction "horizontal"|"vertical"
|
||||||
|
|
||||||
local builtins = {
|
local builtins = {
|
||||||
---@type Builtin.MasterStack
|
---@type Builtin.MasterStack
|
||||||
master_stack = {
|
master_stack = {
|
||||||
|
@ -78,8 +82,8 @@ local builtins = {
|
||||||
---This means that, for example, `inner = 2` will cause the gap
|
---This means that, for example, `inner = 2` will cause the gap
|
||||||
---width between windows to be 4; 2 around each window.
|
---width between windows to be 4; 2 around each window.
|
||||||
---
|
---
|
||||||
---Defaults to 4.
|
---Defaults to 8.
|
||||||
gaps = 4,
|
gaps = 8,
|
||||||
---The proportion of the output taken up by the master window(s).
|
---The proportion of the output taken up by the master window(s).
|
||||||
---
|
---
|
||||||
---This is a float that will be clamped between 0.1 and 0.9
|
---This is a float that will be clamped between 0.1 and 0.9
|
||||||
|
@ -111,8 +115,8 @@ local builtins = {
|
||||||
---This means that, for example, `inner = 2` will cause the gap
|
---This means that, for example, `inner = 2` will cause the gap
|
||||||
---width between windows to be 4; 2 around each window.
|
---width between windows to be 4; 2 around each window.
|
||||||
---
|
---
|
||||||
---Defaults to 4.
|
---Defaults to 8.
|
||||||
gaps = 4,
|
gaps = 8,
|
||||||
---Factors applied to each split.
|
---Factors applied to each split.
|
||||||
---
|
---
|
||||||
---The first split will use the factor at [1],
|
---The first split will use the factor at [1],
|
||||||
|
@ -136,8 +140,8 @@ local builtins = {
|
||||||
---This means that, for example, `inner = 2` will cause the gap
|
---This means that, for example, `inner = 2` will cause the gap
|
||||||
---width between windows to be 4; 2 around each window.
|
---width between windows to be 4; 2 around each window.
|
||||||
---
|
---
|
||||||
---Defaults to 4.
|
---Defaults to 8.
|
||||||
gaps = 4,
|
gaps = 8,
|
||||||
---How much of the output the corner window's width will take up.
|
---How much of the output the corner window's width will take up.
|
||||||
---
|
---
|
||||||
---Defaults to 0.5.
|
---Defaults to 0.5.
|
||||||
|
@ -166,8 +170,8 @@ local builtins = {
|
||||||
---This means that, for example, `inner = 2` will cause the gap
|
---This means that, for example, `inner = 2` will cause the gap
|
||||||
---width between windows to be 4; 2 around each window.
|
---width between windows to be 4; 2 around each window.
|
||||||
---
|
---
|
||||||
---Defaults to 4.
|
---Defaults to 8.
|
||||||
gaps = 4,
|
gaps = 8,
|
||||||
---Factors applied to each split.
|
---Factors applied to each split.
|
||||||
---
|
---
|
||||||
---The first split will use the factor at [1],
|
---The first split will use the factor at [1],
|
||||||
|
@ -176,6 +180,28 @@ local builtins = {
|
||||||
---Defaults to 0.5 if there is no factor at [n].
|
---Defaults to 0.5 if there is no factor at [n].
|
||||||
split_factors = {},
|
split_factors = {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
---@type Builtin.Fair
|
||||||
|
fair = {
|
||||||
|
---Gaps between windows, in pixels.
|
||||||
|
---
|
||||||
|
---This can be an integer or the table { inner: integer, outer: integer }.
|
||||||
|
---If it is an integer, all gaps will be that amount of pixels wide.
|
||||||
|
---If it is a table, `outer` denotes the amount of pixels around the
|
||||||
|
---edge of the output area that will become a gap, and
|
||||||
|
---`inner` denotes the amount of pixels around each window that
|
||||||
|
---will become a gap.
|
||||||
|
---
|
||||||
|
---This means that, for example, `inner = 2` will cause the gap
|
||||||
|
---width between windows to be 4; 2 around each window.
|
||||||
|
---
|
||||||
|
---Defaults to 8.
|
||||||
|
gaps = 8,
|
||||||
|
---The direction of the window lines.
|
||||||
|
---
|
||||||
|
---Defaults to "vertical".
|
||||||
|
direction = "vertical",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param args LayoutArgs
|
---@param args LayoutArgs
|
||||||
|
@ -529,6 +555,8 @@ function builtins.corner:layout(args)
|
||||||
return geos
|
return geos
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Spiral is a copy-paste of dwindle with a minor change, yikes
|
||||||
|
|
||||||
function builtins.spiral:layout(args)
|
function builtins.spiral:layout(args)
|
||||||
local win_count = #args.windows
|
local win_count = #args.windows
|
||||||
|
|
||||||
|
@ -584,9 +612,11 @@ function builtins.spiral:layout(args)
|
||||||
|
|
||||||
local to_push
|
local to_push
|
||||||
|
|
||||||
|
-- Minor change from dwindle here
|
||||||
if i % 4 == 3 or i % 4 == 0 then
|
if i % 4 == 3 or i % 4 == 0 then
|
||||||
rest, to_push = rest:split_at(axis, split_coord, gaps)
|
rest, to_push = rest:split_at(axis, split_coord, gaps)
|
||||||
else
|
else
|
||||||
|
---@diagnostic disable-next-line: cast-local-type
|
||||||
to_push, rest = rest:split_at(axis, split_coord, gaps)
|
to_push, rest = rest:split_at(axis, split_coord, gaps)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -612,6 +642,147 @@ function builtins.spiral:layout(args)
|
||||||
return geos
|
return geos
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function builtins.fair:layout(args)
|
||||||
|
local win_count = #args.windows
|
||||||
|
|
||||||
|
if win_count == 0 then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local width = args.output_width
|
||||||
|
local height = args.output_height
|
||||||
|
|
||||||
|
local rect = require("pinnacle.util").rectangle.new(0, 0, width, height)
|
||||||
|
|
||||||
|
---@type Rectangle[]
|
||||||
|
local geos = {}
|
||||||
|
|
||||||
|
---@type integer
|
||||||
|
local outer_gaps
|
||||||
|
---@type integer?
|
||||||
|
local inner_gaps
|
||||||
|
|
||||||
|
if type(self.gaps) == "number" then
|
||||||
|
outer_gaps = self.gaps --[[@as integer]]
|
||||||
|
else
|
||||||
|
outer_gaps = self.gaps.outer
|
||||||
|
inner_gaps = self.gaps.inner
|
||||||
|
end
|
||||||
|
|
||||||
|
rect = rect:split_at("horizontal", 0, outer_gaps)
|
||||||
|
rect = rect:split_at("horizontal", height - outer_gaps, outer_gaps)
|
||||||
|
rect = rect:split_at("vertical", 0, outer_gaps)
|
||||||
|
rect = rect:split_at("vertical", width - outer_gaps, outer_gaps)
|
||||||
|
|
||||||
|
local gaps = ((not inner_gaps and outer_gaps) or 0)
|
||||||
|
|
||||||
|
if win_count == 1 then
|
||||||
|
table.insert(geos, rect)
|
||||||
|
elseif win_count == 2 then
|
||||||
|
local len
|
||||||
|
if self.direction == "vertical" then
|
||||||
|
len = rect.width
|
||||||
|
else
|
||||||
|
len = rect.height
|
||||||
|
end
|
||||||
|
-- Two windows is special cased to create a new line rather than increase to 2 in a line
|
||||||
|
local rect1, rect2 = rect:split_at(self.direction, len // 2 - gaps // 2, gaps)
|
||||||
|
if rect1 and rect2 then
|
||||||
|
table.insert(geos, rect1)
|
||||||
|
table.insert(geos, rect2)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- 3 / 1
|
||||||
|
-- 7 / 2
|
||||||
|
-- 13 / 3
|
||||||
|
-- 21 / 4
|
||||||
|
|
||||||
|
local line_count = math.floor(math.sqrt(win_count) + 0.5)
|
||||||
|
local wins_per_line = {}
|
||||||
|
local max_per_line = line_count
|
||||||
|
if win_count > line_count * line_count then
|
||||||
|
max_per_line = line_count + 1
|
||||||
|
end
|
||||||
|
for i = 1, win_count do
|
||||||
|
local index = math.ceil(i / max_per_line)
|
||||||
|
if not wins_per_line[index] then
|
||||||
|
wins_per_line[index] = 0
|
||||||
|
end
|
||||||
|
wins_per_line[index] = wins_per_line[index] + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(#wins_per_line == line_count)
|
||||||
|
|
||||||
|
---@type Rectangle[]
|
||||||
|
local line_rects = {}
|
||||||
|
|
||||||
|
local coord
|
||||||
|
local len
|
||||||
|
local axis
|
||||||
|
if self.direction == "horizontal" then
|
||||||
|
coord = rect.y
|
||||||
|
len = rect.height / line_count
|
||||||
|
axis = "horizontal"
|
||||||
|
else
|
||||||
|
coord = rect.x
|
||||||
|
len = rect.width / line_count
|
||||||
|
axis = "vertical"
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, line_count - 1 do
|
||||||
|
local slice_point = coord + math.floor(len * i + 0.5)
|
||||||
|
slice_point = slice_point - gaps // 2
|
||||||
|
local to_push, rest = rect:split_at(axis, slice_point, gaps)
|
||||||
|
table.insert(line_rects, to_push)
|
||||||
|
if not rest then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
rect = rest
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(line_rects, rect)
|
||||||
|
|
||||||
|
for i, line_rect in ipairs(line_rects) do
|
||||||
|
local coord ---@diagnostic disable-line: redefined-local
|
||||||
|
local len ---@diagnostic disable-line: redefined-local
|
||||||
|
local axis ---@diagnostic disable-line: redefined-local
|
||||||
|
if self.direction == "vertical" then
|
||||||
|
coord = line_rect.y
|
||||||
|
len = line_rect.height / wins_per_line[i]
|
||||||
|
axis = "horizontal"
|
||||||
|
else
|
||||||
|
coord = line_rect.x
|
||||||
|
len = line_rect.width / wins_per_line[i]
|
||||||
|
axis = "vertical"
|
||||||
|
end
|
||||||
|
|
||||||
|
for j = 1, wins_per_line[i] - 1 do
|
||||||
|
local slice_point = coord + math.floor(len * j + 0.5)
|
||||||
|
slice_point = slice_point - gaps // 2
|
||||||
|
local to_push, rest = line_rect:split_at(axis, slice_point, gaps)
|
||||||
|
table.insert(geos, to_push)
|
||||||
|
if not rest then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
line_rect = rest
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(geos, line_rect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if inner_gaps then
|
||||||
|
for i = 1, #geos do
|
||||||
|
geos[i].x = geos[i].x + inner_gaps
|
||||||
|
geos[i].y = geos[i].y + inner_gaps
|
||||||
|
geos[i].width = geos[i].width - inner_gaps * 2
|
||||||
|
geos[i].height = geos[i].height - inner_gaps * 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return geos
|
||||||
|
end
|
||||||
|
|
||||||
---@class Layout
|
---@class Layout
|
||||||
---@field private stream H2Stream?
|
---@field private stream H2Stream?
|
||||||
local layout = {
|
local layout = {
|
||||||
|
@ -649,7 +820,13 @@ function layout.set_manager(manager)
|
||||||
output_height = response.output_height,
|
output_height = response.output_height,
|
||||||
}
|
}
|
||||||
|
|
||||||
local geos = manager:get_active(args):layout(args)
|
local a = manager:get_active(args)
|
||||||
|
local success, geos = pcall(a.layout, a, args)
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
print(geos)
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
local body = protobuf.encode(".pinnacle.layout.v0alpha1.LayoutRequest", {
|
local body = protobuf.encode(".pinnacle.layout.v0alpha1.LayoutRequest", {
|
||||||
geometries = {
|
geometries = {
|
||||||
|
@ -701,12 +878,14 @@ local CyclingLayoutManager = {}
|
||||||
function CyclingLayoutManager:get_active(args)
|
function CyclingLayoutManager:get_active(args)
|
||||||
local first_tag = args.tags[1]
|
local first_tag = args.tags[1]
|
||||||
|
|
||||||
|
-- Return a no-op generator if there are no active tags
|
||||||
if not first_tag then
|
if not first_tag then
|
||||||
---@type LayoutGenerator
|
---@type LayoutGenerator
|
||||||
return {
|
return {
|
||||||
layout = function(_, _)
|
layout = function(_, _)
|
||||||
return {}
|
return {}
|
||||||
end,
|
end,
|
||||||
|
gaps = 0,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -91,18 +91,18 @@ local Rectangle = {}
|
||||||
|
|
||||||
---Split this rectangle along `axis` at `at`.
|
---Split this rectangle along `axis` at `at`.
|
||||||
---
|
---
|
||||||
---If `at2` is specified, the split will chop off a section of this
|
---If `thickness` is specified, the split will chop off a section of this
|
||||||
---rectangle from `at` to `at2`.
|
---rectangle from `at` to `at + thickness`.
|
||||||
---
|
---
|
||||||
---`at` and `at2` are relative to the space this rectangle is in, not
|
---`at` is relative to the space this rectangle is in, not
|
||||||
---this rectangle's origin.
|
---this rectangle's origin.
|
||||||
---
|
---
|
||||||
---@param axis "horizontal" | "vertical"
|
---@param axis "horizontal" | "vertical"
|
||||||
---@param at number
|
---@param at number
|
||||||
---@param thickness? number
|
---@param thickness number?
|
||||||
---
|
---
|
||||||
---@return Rectangle rect1 The first rectangle.
|
---@return Rectangle rect1 The first rectangle.
|
||||||
---@return Rectangle? rect2 The seoond rectangle, if there is one.
|
---@return Rectangle|nil rect2 The seoond rectangle, if there is one.
|
||||||
function Rectangle:split_at(axis, at, thickness)
|
function Rectangle:split_at(axis, at, thickness)
|
||||||
---@diagnostic disable-next-line: redefined-local
|
---@diagnostic disable-next-line: redefined-local
|
||||||
local thickness = thickness or 0
|
local thickness = thickness or 0
|
||||||
|
@ -173,8 +173,9 @@ function Rectangle:split_at(axis, at, thickness)
|
||||||
|
|
||||||
return rect1, rect2
|
return rect1, rect2
|
||||||
end
|
end
|
||||||
end -- TODO: handle error if neither
|
end
|
||||||
|
|
||||||
|
print("Invalid axis:", axis)
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -264,7 +264,7 @@ impl State {
|
||||||
anyhow::bail!("Attempted to layout but the request was nonexistent A");
|
anyhow::bail!("Attempted to layout but the request was nonexistent A");
|
||||||
};
|
};
|
||||||
|
|
||||||
if dbg!(latest) == dbg!(request_id) {
|
if latest == request_id {
|
||||||
pending.pop();
|
pending.pop();
|
||||||
} else if let Some(pos) = pending
|
} else if let Some(pos) = pending
|
||||||
.split_last()
|
.split_last()
|
||||||
|
|
Loading…
Reference in a new issue