mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-14 08:01:14 +01:00
Add layout cycling, dwindle, remove old layout stuff
This commit is contained in:
parent
b3ba9f9393
commit
e1f2706428
12 changed files with 347 additions and 585 deletions
|
@ -88,11 +88,48 @@ require("pinnacle").setup(function(Pinnacle)
|
||||||
-- Layouts --
|
-- Layouts --
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
local layout_handler = Layout.new_handler({
|
local layout_manager = Layout.new_cycling_manager({
|
||||||
Layout.builtins.master_stack,
|
Layout.builtins.master_stack,
|
||||||
|
Layout.builtins.dwindle,
|
||||||
})
|
})
|
||||||
|
|
||||||
Layout.set_handler(layout_handler)
|
Layout.set_manager(layout_manager)
|
||||||
|
|
||||||
|
-- mod_key + space = Cycle forward one layout on the focused output
|
||||||
|
Input.keybind({ mod_key }, key.space, function()
|
||||||
|
local focused_op = Output.get_focused()
|
||||||
|
if focused_op then
|
||||||
|
local tags = focused_op:tags()
|
||||||
|
local tag = nil
|
||||||
|
for _, t in ipairs(tags or {}) do
|
||||||
|
if t:active() then
|
||||||
|
tag = t
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if tag then
|
||||||
|
layout_manager:cycle_layout_forward(tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- mod_key + shift + space = Cycle backward one layout on the focused output
|
||||||
|
Input.keybind({ mod_key, "shift" }, key.space, function()
|
||||||
|
local focused_op = Output.get_focused()
|
||||||
|
if focused_op then
|
||||||
|
local tags = focused_op:tags()
|
||||||
|
local tag = nil
|
||||||
|
for _, t in ipairs(tags or {}) do
|
||||||
|
if t:active() then
|
||||||
|
tag = t
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if tag then
|
||||||
|
layout_manager:cycle_layout_backward(tag)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
-- Spawning must happen after you add tags, as Pinnacle currently doesn't render windows without tags.
|
-- Spawning must happen after you add tags, as Pinnacle currently doesn't render windows without tags.
|
||||||
Process.spawn_once(terminal)
|
Process.spawn_once(terminal)
|
||||||
|
|
|
@ -38,48 +38,79 @@ end
|
||||||
---@field output_width integer
|
---@field output_width integer
|
||||||
---@field output_height integer
|
---@field output_height integer
|
||||||
|
|
||||||
---@class Builtin
|
---A layout generator.
|
||||||
|
---@class LayoutGenerator
|
||||||
|
---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 }[]
|
||||||
|
|
||||||
---@class Builtin.MasterStack : Builtin
|
---@class Builtin.MasterStack : LayoutGenerator
|
||||||
---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 4.
|
|
||||||
---@field gaps integer | { inner: integer, outer: integer }
|
---@field gaps integer | { inner: integer, outer: integer }
|
||||||
---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
|
|
||||||
---similarly to River.
|
|
||||||
---
|
|
||||||
---Defaults to 0.5.
|
|
||||||
---@field master_factor number
|
---@field master_factor number
|
||||||
---The side the master window(s) will be on.
|
|
||||||
---
|
|
||||||
---Defaults to `"left"`.
|
|
||||||
---@field master_side "left"|"right"|"top"|"bottom"
|
---@field master_side "left"|"right"|"top"|"bottom"
|
||||||
---How many windows the master side will have.
|
|
||||||
---
|
|
||||||
---Defaults to 1.
|
|
||||||
---@field master_count integer
|
---@field master_count integer
|
||||||
|
|
||||||
|
---@class Builtin.Dwindle : LayoutGenerator
|
||||||
|
---@field gaps integer | { inner: integer, outer: integer }
|
||||||
|
---@field split_factors table<integer, number>
|
||||||
|
|
||||||
local builtins = {
|
local builtins = {
|
||||||
---@type Builtin.MasterStack
|
---@type Builtin.MasterStack
|
||||||
master_stack = {
|
master_stack = {
|
||||||
|
---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 4.
|
||||||
gaps = 4,
|
gaps = 4,
|
||||||
|
---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
|
||||||
|
---similarly to River.
|
||||||
|
---
|
||||||
|
---Defaults to 0.5.
|
||||||
master_factor = 0.5,
|
master_factor = 0.5,
|
||||||
|
---The side the master window(s) will be on.
|
||||||
|
---
|
||||||
|
---Defaults to `"left"`.
|
||||||
master_side = "left",
|
master_side = "left",
|
||||||
|
---How many windows the master side will have.
|
||||||
|
---
|
||||||
|
---Defaults to 1.
|
||||||
master_count = 1,
|
master_count = 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
---@type Builtin.Dwindle
|
||||||
|
dwindle = {
|
||||||
|
---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 4.
|
||||||
|
gaps = 4,
|
||||||
|
---Factors applied to each split.
|
||||||
|
---
|
||||||
|
---The first split will use the factor at [1],
|
||||||
|
---the second at [2], and so on.
|
||||||
|
---
|
||||||
|
---Defaults to 0.5 if there is no factor at [n].
|
||||||
|
split_factors = {},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param args LayoutArgs
|
---@param args LayoutArgs
|
||||||
|
@ -241,20 +272,137 @@ function builtins.master_stack:layout(args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param args LayoutArgs
|
||||||
|
---
|
||||||
|
---@return { x: integer, y: integer, width: integer, height: integer }[]
|
||||||
|
function builtins.dwindle: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 = {}
|
||||||
|
|
||||||
|
if type(self.gaps) == "number" then
|
||||||
|
local gaps = self.gaps --[[@as integer]]
|
||||||
|
|
||||||
|
rect = rect:split_at("horizontal", 0, gaps)
|
||||||
|
rect = rect:split_at("horizontal", height - gaps, gaps)
|
||||||
|
rect = rect:split_at("vertical", 0, gaps)
|
||||||
|
rect = rect:split_at("vertical", width - gaps, gaps)
|
||||||
|
|
||||||
|
if win_count == 1 then
|
||||||
|
table.insert(geos, rect)
|
||||||
|
return geos
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type Rectangle
|
||||||
|
local rest = rect
|
||||||
|
|
||||||
|
for i = 1, win_count - 1 do
|
||||||
|
local factor = math.min(math.max(self.split_factors[i] or 0.5, 0.1), 0.9)
|
||||||
|
local axis
|
||||||
|
local split_coord
|
||||||
|
if i % 2 == 1 then
|
||||||
|
axis = "vertical"
|
||||||
|
split_coord = rest.x + math.floor(rest.width * factor + 0.5)
|
||||||
|
else
|
||||||
|
axis = "horizontal"
|
||||||
|
split_coord = rest.y + math.floor(rest.height * factor + 0.5)
|
||||||
|
end
|
||||||
|
split_coord = split_coord - gaps // 2
|
||||||
|
|
||||||
|
local to_push
|
||||||
|
|
||||||
|
to_push, rest = rest:split_at(axis, split_coord, gaps)
|
||||||
|
|
||||||
|
if not rest then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(geos, to_push)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(geos, rest)
|
||||||
|
|
||||||
|
return geos
|
||||||
|
else
|
||||||
|
local inner_gaps = self.gaps.inner
|
||||||
|
local outer_gaps = self.gaps.outer
|
||||||
|
|
||||||
|
if win_count == 1 then
|
||||||
|
rect = rect:split_at("horizontal", 0, (inner_gaps + outer_gaps))
|
||||||
|
rect = rect:split_at("horizontal", height - (inner_gaps + outer_gaps), (inner_gaps + outer_gaps))
|
||||||
|
rect = rect:split_at("vertical", 0, (inner_gaps + outer_gaps))
|
||||||
|
rect = rect:split_at("vertical", width - (inner_gaps + outer_gaps), (inner_gaps + outer_gaps))
|
||||||
|
table.insert(geos, rect)
|
||||||
|
return geos
|
||||||
|
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)
|
||||||
|
|
||||||
|
---@type Rectangle
|
||||||
|
local rest = rect
|
||||||
|
|
||||||
|
for i = 1, win_count - 1 do
|
||||||
|
local factor = math.min(math.max(self.split_factors[i] or 0.5, 0.1), 0.9)
|
||||||
|
local axis
|
||||||
|
local split_coord
|
||||||
|
if i % 2 == 1 then
|
||||||
|
axis = "vertical"
|
||||||
|
split_coord = rest.x + math.floor(rest.width * factor + 0.5)
|
||||||
|
else
|
||||||
|
axis = "horizontal"
|
||||||
|
split_coord = rest.y + math.floor(rest.height * factor + 0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
local to_push
|
||||||
|
|
||||||
|
to_push, rest = rest:split_at(axis, split_coord)
|
||||||
|
|
||||||
|
if not rest then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(geos, to_push)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(geos, rest)
|
||||||
|
|
||||||
|
for i = 1, #geos do
|
||||||
|
geos[i] = geos[i]:split_at("horizontal", geos[i].y, inner_gaps)
|
||||||
|
geos[i] = geos[i]:split_at("horizontal", geos[i].y + geos[i].height - inner_gaps, inner_gaps)
|
||||||
|
geos[i] = geos[i]:split_at("vertical", geos[i].x, inner_gaps)
|
||||||
|
geos[i] = geos[i]:split_at("vertical", geos[i].x + geos[i].width - inner_gaps, inner_gaps)
|
||||||
|
end
|
||||||
|
|
||||||
|
return geos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@class Layout
|
---@class Layout
|
||||||
local layout = {
|
local layout = {
|
||||||
builtins = builtins,
|
builtins = builtins,
|
||||||
}
|
}
|
||||||
|
|
||||||
---@param handler LayoutHandler
|
---@param manager LayoutManager
|
||||||
function layout.set_handler(handler)
|
function layout.set_manager(manager)
|
||||||
client.bidirectional_streaming_request(
|
client.bidirectional_streaming_request(
|
||||||
build_grpc_request_params("Layout", {
|
build_grpc_request_params("Layout", {
|
||||||
layout = {},
|
layout = {},
|
||||||
}),
|
}),
|
||||||
function(response, stream)
|
function(response, stream)
|
||||||
local request_id = response.request_id
|
local request_id = response.request_id
|
||||||
local index = handler.index
|
|
||||||
|
|
||||||
---@diagnostic disable-next-line: invisible
|
---@diagnostic disable-next-line: invisible
|
||||||
local output_handle = require("pinnacle.output").handle.new(response.output_name)
|
local output_handle = require("pinnacle.output").handle.new(response.output_name)
|
||||||
|
@ -274,7 +422,7 @@ function layout.set_handler(handler)
|
||||||
output_height = response.output_height,
|
output_height = response.output_height,
|
||||||
}
|
}
|
||||||
|
|
||||||
local geos = handler.layouts[index]:layout(args)
|
local geos = manager:get_active(args):layout(args)
|
||||||
|
|
||||||
local body = protobuf.encode(".pinnacle.layout.v0alpha1.LayoutRequest", {
|
local body = protobuf.encode(".pinnacle.layout.v0alpha1.LayoutRequest", {
|
||||||
geometries = {
|
geometries = {
|
||||||
|
@ -289,33 +437,81 @@ function layout.set_handler(handler)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class LayoutHandlerModule
|
---@class LayoutManager
|
||||||
local layout_handler = {}
|
---@field layouts LayoutGenerator[]
|
||||||
|
---@field get_active fun(self: self, args: LayoutArgs): LayoutGenerator
|
||||||
|
|
||||||
---@class LayoutHandler
|
---A `LayoutManager` that keeps track of layouts per tag and provides
|
||||||
|
---methods to cycle between them.
|
||||||
|
---@class CyclingLayoutManager : LayoutManager
|
||||||
---@field index integer
|
---@field index integer
|
||||||
---@field layouts { layout: fun(self: self, args: LayoutArgs): { x: integer, y: integer, width: integer, height: integer }[] }[]
|
local CyclingLayoutManager = {
|
||||||
local LayoutHandler = {}
|
---@type table<integer, integer>
|
||||||
|
tag_indices = {},
|
||||||
|
}
|
||||||
|
|
||||||
---@param layouts { layout: fun(self: self, args: LayoutArgs): { x: integer, y: integer, width: integer, height: integer }[] }[]
|
---@param args LayoutArgs
|
||||||
---@return LayoutHandler
|
---@return LayoutGenerator
|
||||||
function layout_handler.new(layouts)
|
function CyclingLayoutManager:get_active(args)
|
||||||
---@type LayoutHandler
|
local first_tag = args.tags[1]
|
||||||
|
|
||||||
|
if not first_tag then
|
||||||
|
---@type LayoutGenerator
|
||||||
|
return {
|
||||||
|
layout = function(_, _)
|
||||||
|
return {}
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.tag_indices[first_tag.id] then
|
||||||
|
self.tag_indices[first_tag.id] = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return self.layouts[self.tag_indices[first_tag.id]]
|
||||||
|
end
|
||||||
|
|
||||||
|
---Cycle the layout for the given tag forward.
|
||||||
|
---@param tag TagHandle
|
||||||
|
function CyclingLayoutManager:cycle_layout_forward(tag)
|
||||||
|
if not self.tag_indices[tag.id] then
|
||||||
|
self.tag_indices[tag.id] = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
self.tag_indices[tag.id] = self.tag_indices[tag.id] + 1
|
||||||
|
|
||||||
|
if self.tag_indices[tag.id] > #self.layouts then
|
||||||
|
self.tag_indices[tag.id] = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Cycle the layout for the given tag backward.
|
||||||
|
---@param tag TagHandle
|
||||||
|
function CyclingLayoutManager:cycle_layout_backward(tag)
|
||||||
|
if not self.tag_indices[tag.id] then
|
||||||
|
self.tag_indices[tag.id] = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
self.tag_indices[tag.id] = self.tag_indices[tag.id] - 1
|
||||||
|
|
||||||
|
if self.tag_indices[tag.id] < 1 then
|
||||||
|
self.tag_indices[tag.id] = #self.layouts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param layouts LayoutGenerator[]
|
||||||
|
---
|
||||||
|
---@return CyclingLayoutManager
|
||||||
|
function layout.new_cycling_manager(layouts)
|
||||||
|
---@type CyclingLayoutManager
|
||||||
local self = {
|
local self = {
|
||||||
index = 1,
|
index = 1,
|
||||||
layouts = layouts,
|
layouts = layouts,
|
||||||
}
|
}
|
||||||
|
|
||||||
setmetatable(self, { __index = LayoutHandler })
|
setmetatable(self, { __index = CyclingLayoutManager })
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param layouts { layout: fun(self: self, args: LayoutArgs): { x: integer, y: integer, width: integer, height: integer }[] }[]
|
|
||||||
---
|
|
||||||
---@return LayoutHandler
|
|
||||||
function layout.new_handler(layouts)
|
|
||||||
return layout_handler.new(layouts)
|
|
||||||
end
|
|
||||||
|
|
||||||
return layout
|
return layout
|
||||||
|
|
|
@ -191,12 +191,6 @@ function rectangle.new(x, y, width, height)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
local r = rectangle.new(0, 0, 100, 100)
|
|
||||||
local r1, r2 = r:split_at("horizontal", 96, 4)
|
|
||||||
|
|
||||||
print(require("inspect")(r1))
|
|
||||||
print(require("inspect")(r2))
|
|
||||||
|
|
||||||
util.rectangle = rectangle
|
util.rectangle = rectangle
|
||||||
|
|
||||||
return util
|
return util
|
||||||
|
|
47
src/api.rs
47
src/api.rs
|
@ -703,7 +703,7 @@ impl tag_service_server::TagService for TagService {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.update_focus(&output);
|
state.update_focus(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
})
|
})
|
||||||
|
@ -730,7 +730,7 @@ impl tag_service_server::TagService for TagService {
|
||||||
tag.set_active(true);
|
tag.set_active(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.update_focus(&output);
|
state.update_focus(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
})
|
})
|
||||||
|
@ -818,7 +818,7 @@ impl tag_service_server::TagService for TagService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,40 +831,13 @@ impl tag_service_server::TagService for TagService {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_layout(&self, request: Request<SetLayoutRequest>) -> Result<Response<()>, Status> {
|
async fn set_layout(
|
||||||
let request = request.into_inner();
|
&self,
|
||||||
|
_request: Request<SetLayoutRequest>,
|
||||||
|
) -> Result<Response<()>, Status> {
|
||||||
|
warn!("Tag.set_layout has been deprecated");
|
||||||
|
|
||||||
let tag_id = TagId(
|
run_unary_no_response(&self.sender, move |_state| {}).await
|
||||||
request
|
|
||||||
.tag_id
|
|
||||||
.ok_or_else(|| Status::invalid_argument("no tag specified"))?,
|
|
||||||
);
|
|
||||||
|
|
||||||
use pinnacle_api_defs::pinnacle::tag::v0alpha1::set_layout_request::Layout;
|
|
||||||
|
|
||||||
// TODO: from impl
|
|
||||||
let layout = match request.layout() {
|
|
||||||
Layout::Unspecified => return Err(Status::invalid_argument("unspecified layout")),
|
|
||||||
Layout::MasterStack => crate::layout::Layout::MasterStack,
|
|
||||||
Layout::Dwindle => crate::layout::Layout::Dwindle,
|
|
||||||
Layout::Spiral => crate::layout::Layout::Spiral,
|
|
||||||
Layout::CornerTopLeft => crate::layout::Layout::CornerTopLeft,
|
|
||||||
Layout::CornerTopRight => crate::layout::Layout::CornerTopRight,
|
|
||||||
Layout::CornerBottomLeft => crate::layout::Layout::CornerBottomLeft,
|
|
||||||
Layout::CornerBottomRight => crate::layout::Layout::CornerBottomRight,
|
|
||||||
};
|
|
||||||
|
|
||||||
run_unary_no_response(&self.sender, move |state| {
|
|
||||||
let Some(tag) = tag_id.tag(state) else { return };
|
|
||||||
|
|
||||||
tag.set_layout(layout);
|
|
||||||
|
|
||||||
let Some(output) = tag.output(state) else { return };
|
|
||||||
|
|
||||||
state.update_windows(&output);
|
|
||||||
state.schedule_render(&output);
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(
|
async fn get(
|
||||||
|
@ -975,7 +948,7 @@ impl output_service_server::OutputService for OutputService {
|
||||||
output.change_current_state(None, None, None, Some(loc));
|
output.change_current_state(None, None, None, Some(loc));
|
||||||
state.space.map_output(&output, loc);
|
state.space.map_output(&output, loc);
|
||||||
tracing::debug!("Mapping output {} to {loc:?}", output.name());
|
tracing::debug!("Mapping output {} to {loc:?}", output.name());
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ impl layout_service_server::LayoutService for LayoutService {
|
||||||
if let Some(body) = request.body {
|
if let Some(body) = request.body {
|
||||||
match body {
|
match body {
|
||||||
layout_request::Body::Geometries(geos) => {
|
layout_request::Body::Geometries(geos) => {
|
||||||
// dbg!(&geos);
|
|
||||||
if let Err(err) = state.apply_layout(geos) {
|
if let Err(err) = state.apply_layout(geos) {
|
||||||
// TODO: send a Status and handle the error client side
|
// TODO: send a Status and handle the error client side
|
||||||
tracing::error!("{err}")
|
tracing::error!("{err}")
|
||||||
|
@ -46,9 +45,9 @@ impl layout_service_server::LayoutService for LayoutService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => (),
|
Err(err) => tracing::error!("{err}"),
|
||||||
},
|
},
|
||||||
|state, sender, join_handle| {
|
|state, sender, _join_handle| {
|
||||||
state.layout_state.layout_request_sender = Some(sender);
|
state.layout_state.layout_request_sender = Some(sender);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -114,7 +114,7 @@ impl window_service_server::WindowService for WindowService {
|
||||||
});
|
});
|
||||||
|
|
||||||
for output in state.space.outputs_for_element(&window) {
|
for output in state.space.outputs_for_element(&window) {
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -163,7 +163,7 @@ impl window_service_server::WindowService for WindowService {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -211,7 +211,7 @@ impl window_service_server::WindowService for WindowService {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -259,7 +259,7 @@ impl window_service_server::WindowService for WindowService {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -349,7 +349,7 @@ impl window_service_server::WindowService for WindowService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -380,7 +380,7 @@ impl window_service_server::WindowService for WindowService {
|
||||||
state.tags = vec![tag.clone()];
|
state.tags = vec![tag.clone()];
|
||||||
});
|
});
|
||||||
let Some(output) = tag.output(state) else { return };
|
let Some(output) = tag.output(state) else { return };
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -431,7 +431,7 @@ impl window_service_server::WindowService for WindowService {
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(output) = tag.output(state) else { return };
|
let Some(output) = tag.output(state) else { return };
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
state.schedule_render(&output);
|
state.schedule_render(&output);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -233,7 +233,7 @@ pub fn setup_winit(
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
layer_map_for_output(&output).arrange();
|
layer_map_for_output(&output).arrange();
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
}
|
}
|
||||||
WinitEvent::Focus(_) => {}
|
WinitEvent::Focus(_) => {}
|
||||||
WinitEvent::Input(input_evt) => {
|
WinitEvent::Input(input_evt) => {
|
||||||
|
|
|
@ -161,7 +161,7 @@ impl CompositorHandler for State {
|
||||||
self.apply_window_rules(&new_window);
|
self.apply_window_rules(&new_window);
|
||||||
|
|
||||||
if let Some(focused_output) = self.focused_output().cloned() {
|
if let Some(focused_output) = self.focused_output().cloned() {
|
||||||
self.update_windows(&focused_output);
|
self.request_layout(&focused_output);
|
||||||
new_window.send_frame(
|
new_window.send_frame(
|
||||||
&focused_output,
|
&focused_output,
|
||||||
self.clock.now(),
|
self.clock.now(),
|
||||||
|
@ -514,7 +514,7 @@ impl WlrLayerShellHandler for State {
|
||||||
drop(map); // wow i really love refcells haha
|
drop(map); // wow i really love refcells haha
|
||||||
|
|
||||||
self.loop_handle.insert_idle(move |state| {
|
self.loop_handle.insert_idle(move |state| {
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,7 +534,7 @@ impl WlrLayerShellHandler for State {
|
||||||
|
|
||||||
if let Some(output) = output {
|
if let Some(output) = output {
|
||||||
self.loop_handle.insert_idle(move |state| {
|
self.loop_handle.insert_idle(move |state| {
|
||||||
state.update_windows(&output);
|
state.request_layout(&output);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl XdgShellHandler for State {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(output) = window.output(self) {
|
if let Some(output) = window.output(self) {
|
||||||
self.update_windows(&output);
|
self.request_layout(&output);
|
||||||
let focus = self
|
let focus = self
|
||||||
.focused_window(&output)
|
.focused_window(&output)
|
||||||
.map(KeyboardFocusTarget::Window);
|
.map(KeyboardFocusTarget::Window);
|
||||||
|
@ -766,7 +766,7 @@ impl XdgShellHandler for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(output) = window.output(self) else { return };
|
let Some(output) = window.output(self) else { return };
|
||||||
self.update_windows(&output);
|
self.request_layout(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
|
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
|
||||||
|
@ -779,7 +779,7 @@ impl XdgShellHandler for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(output) = window.output(self) else { return };
|
let Some(output) = window.output(self) else { return };
|
||||||
self.update_windows(&output);
|
self.request_layout(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn minimize_request(&mut self, _surface: ToplevelSurface) {
|
fn minimize_request(&mut self, _surface: ToplevelSurface) {
|
||||||
|
|
|
@ -103,7 +103,7 @@ impl XwmHandler for State {
|
||||||
|
|
||||||
if let Some(output) = window.output(self) {
|
if let Some(output) = window.output(self) {
|
||||||
output.with_state_mut(|state| state.focus_stack.set_focus(window.clone()));
|
output.with_state_mut(|state| state.focus_stack.set_focus(window.clone()));
|
||||||
self.update_windows(&output);
|
self.request_layout(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.loop_handle.insert_idle(move |state| {
|
self.loop_handle.insert_idle(move |state| {
|
||||||
|
@ -166,7 +166,7 @@ impl XwmHandler for State {
|
||||||
self.space.unmap_elem(&win);
|
self.space.unmap_elem(&win);
|
||||||
|
|
||||||
if let Some(output) = win.output(self) {
|
if let Some(output) = win.output(self) {
|
||||||
self.update_windows(&output);
|
self.request_layout(&output);
|
||||||
|
|
||||||
let focus = self
|
let focus = self
|
||||||
.focused_window(&output)
|
.focused_window(&output)
|
||||||
|
@ -227,7 +227,7 @@ impl XwmHandler for State {
|
||||||
.retain(|elem| win.wl_surface() != elem.wl_surface());
|
.retain(|elem| win.wl_surface() != elem.wl_surface());
|
||||||
|
|
||||||
if let Some(output) = win.output(self) {
|
if let Some(output) = win.output(self) {
|
||||||
self.update_windows(&output);
|
self.request_layout(&output);
|
||||||
|
|
||||||
let focus = self
|
let focus = self
|
||||||
.focused_window(&output)
|
.focused_window(&output)
|
||||||
|
|
491
src/layout.rs
491
src/layout.rs
|
@ -6,7 +6,7 @@ use pinnacle_api_defs::pinnacle::layout::v0alpha1::{layout_request::Geometries,
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::{layer_map_for_output, WindowSurface},
|
desktop::{layer_map_for_output, WindowSurface},
|
||||||
output::Output,
|
output::Output,
|
||||||
utils::{Logical, Point, Rectangle, Serial, Size},
|
utils::{Logical, Point, Rectangle, Serial},
|
||||||
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
|
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
@ -23,39 +23,6 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Compute the positions and sizes of tiled windows on
|
|
||||||
/// `output` according to the provided [`Layout`].
|
|
||||||
fn tile_windows(&self, output: &Output, windows: Vec<WindowElement>, layout: Layout) {
|
|
||||||
let Some(rect) = self.space.output_geometry(output).map(|op_geo| {
|
|
||||||
let map = layer_map_for_output(output);
|
|
||||||
if map.layers().peekable().peek().is_none() {
|
|
||||||
// INFO: Sometimes the exclusive zone is some weird number that doesn't match the
|
|
||||||
// | output res, even when there are no layer surfaces mapped. In this case, we
|
|
||||||
// | just return the output geometry.
|
|
||||||
op_geo
|
|
||||||
} else {
|
|
||||||
let zone = map.non_exclusive_zone();
|
|
||||||
tracing::debug!("non_exclusive_zone is {zone:?}");
|
|
||||||
Rectangle::from_loc_and_size(op_geo.loc + zone.loc, zone.size)
|
|
||||||
}
|
|
||||||
}) else {
|
|
||||||
// TODO: maybe default to something like 800x800 like in anvil so people still see
|
|
||||||
// | windows open
|
|
||||||
error!("Failed to get output geometry");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
match layout {
|
|
||||||
Layout::MasterStack => master_stack(windows, rect),
|
|
||||||
Layout::Dwindle => dwindle(windows, rect),
|
|
||||||
Layout::Spiral => spiral(windows, rect),
|
|
||||||
layout @ (Layout::CornerTopLeft
|
|
||||||
| Layout::CornerTopRight
|
|
||||||
| Layout::CornerBottomLeft
|
|
||||||
| Layout::CornerBottomRight) => corner(&layout, windows, rect),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_windows_with_geometries(
|
pub fn update_windows_with_geometries(
|
||||||
&mut self,
|
&mut self,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
|
@ -163,10 +130,36 @@ impl State {
|
||||||
self.fixup_z_layering();
|
self.fixup_z_layering();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_windows(&mut self, output: &Output) {
|
/// Swaps two windows in the main window vec and updates all windows.
|
||||||
let Some(layout) =
|
pub fn swap_window_positions(&mut self, win1: &WindowElement, win2: &WindowElement) {
|
||||||
output.with_state(|state| state.focused_tags().next().map(|tag| tag.layout()))
|
let win1_index = self.windows.iter().position(|win| win == win1);
|
||||||
else {
|
let win2_index = self.windows.iter().position(|win| win == win2);
|
||||||
|
|
||||||
|
if let (Some(first), Some(second)) = (win1_index, win2_index) {
|
||||||
|
self.windows.swap(first, second);
|
||||||
|
if let Some(output) = win1.output(self) {
|
||||||
|
self.request_layout(&output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A monotonically increasing identifier for layout requests.
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct LayoutRequestId(pub u32);
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct LayoutState {
|
||||||
|
pub layout_request_sender: Option<UnboundedSender<Result<LayoutResponse, Status>>>,
|
||||||
|
id_maps: HashMap<Output, LayoutRequestId>,
|
||||||
|
pending_requests: HashMap<Output, Vec<(LayoutRequestId, Vec<WindowElement>)>>,
|
||||||
|
old_requests: HashMap<Output, HashSet<LayoutRequestId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn request_layout(&mut self, output: &Output) {
|
||||||
|
let Some(sender) = self.layout_state.layout_request_sender.as_ref() else {
|
||||||
|
error!("Layout requested but no client has connected to the layout service");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -182,7 +175,7 @@ impl State {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
});
|
});
|
||||||
|
|
||||||
let tiled_windows = windows_on_foc_tags
|
let windows = windows_on_foc_tags
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|win| {
|
.filter(|win| {
|
||||||
win.with_state(|state| {
|
win.with_state(|state| {
|
||||||
|
@ -192,425 +185,9 @@ impl State {
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
self.layout_request(output.clone(), tiled_windows);
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
self.tile_windows(output, tiled_windows, layout);
|
|
||||||
|
|
||||||
let output_geo = self.space.output_geometry(output).expect("no output geo");
|
|
||||||
for window in windows_on_foc_tags.iter() {
|
|
||||||
match window.with_state(|state| state.fullscreen_or_maximized) {
|
|
||||||
FullscreenOrMaximized::Fullscreen => {
|
|
||||||
window.change_geometry(output_geo);
|
|
||||||
}
|
|
||||||
FullscreenOrMaximized::Maximized => {
|
|
||||||
let map = layer_map_for_output(output);
|
|
||||||
let geo = if map.layers().next().is_none() {
|
|
||||||
// INFO: Sometimes the exclusive zone is some weird number that doesn't match the
|
|
||||||
// | output res, even when there are no layer surfaces mapped. In this case, we
|
|
||||||
// | just return the output geometry.
|
|
||||||
output_geo
|
|
||||||
} else {
|
|
||||||
let zone = map.non_exclusive_zone();
|
|
||||||
tracing::debug!("non_exclusive_zone is {zone:?}");
|
|
||||||
Rectangle::from_loc_and_size(output_geo.loc + zone.loc, zone.size)
|
|
||||||
};
|
|
||||||
window.change_geometry(geo);
|
|
||||||
}
|
|
||||||
FullscreenOrMaximized::Neither => {
|
|
||||||
if let FloatingOrTiled::Floating(rect) =
|
|
||||||
window.with_state(|state| state.floating_or_tiled)
|
|
||||||
{
|
|
||||||
window.change_geometry(rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut pending_wins = Vec::<(WindowElement, Serial)>::new();
|
|
||||||
let mut non_pending_wins = Vec::<(Point<i32, Logical>, WindowElement)>::new();
|
|
||||||
|
|
||||||
for win in windows_on_foc_tags.iter() {
|
|
||||||
if win.with_state(|state| state.target_loc.is_some()) {
|
|
||||||
match win.underlying_surface() {
|
|
||||||
WindowSurface::Wayland(toplevel) => {
|
|
||||||
let pending = compositor::with_states(toplevel.wl_surface(), |states| {
|
|
||||||
states
|
|
||||||
.data_map
|
|
||||||
.get::<XdgToplevelSurfaceData>()
|
|
||||||
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to lock Mutex<XdgToplevelSurfaceData>")
|
|
||||||
.has_pending_changes()
|
|
||||||
});
|
|
||||||
|
|
||||||
if pending {
|
|
||||||
pending_wins.push((win.clone(), toplevel.send_configure()))
|
|
||||||
} else {
|
|
||||||
let loc = win.with_state_mut(|state| state.target_loc.take());
|
|
||||||
if let Some(loc) = loc {
|
|
||||||
non_pending_wins.push((loc, win.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowSurface::X11(_) => {
|
|
||||||
let loc = win.with_state_mut(|state| state.target_loc.take());
|
|
||||||
if let Some(loc) = loc {
|
|
||||||
self.space.map_element(win.clone(), loc, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (loc, window) in non_pending_wins {
|
|
||||||
self.space.map_element(window, loc, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fixup_z_layering();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub enum Layout {
|
|
||||||
MasterStack,
|
|
||||||
Dwindle,
|
|
||||||
Spiral,
|
|
||||||
CornerTopLeft,
|
|
||||||
CornerTopRight,
|
|
||||||
CornerBottomLeft,
|
|
||||||
CornerBottomRight,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn master_stack(windows: Vec<WindowElement>, rect: Rectangle<i32, Logical>) {
|
|
||||||
let size = rect.size;
|
|
||||||
let loc = rect.loc;
|
|
||||||
|
|
||||||
let master = windows.first();
|
|
||||||
let stack = windows.iter().skip(1);
|
|
||||||
|
|
||||||
let Some(master) = master else { return };
|
|
||||||
|
|
||||||
let stack_count = stack.clone().count();
|
|
||||||
|
|
||||||
if stack_count == 0 {
|
|
||||||
// one window
|
|
||||||
master.change_geometry(Rectangle::from_loc_and_size(loc, size));
|
|
||||||
} else {
|
|
||||||
let loc: Point<i32, Logical> = (loc.x, loc.y).into();
|
|
||||||
let new_master_size: Size<i32, Logical> = (size.w / 2, size.h).into();
|
|
||||||
master.change_geometry(Rectangle::from_loc_and_size(loc, new_master_size));
|
|
||||||
|
|
||||||
let height = size.h as f32 / stack_count as f32;
|
|
||||||
let mut y_s = vec![];
|
|
||||||
for i in 0..stack_count {
|
|
||||||
y_s.push((i as f32 * height).round() as i32);
|
|
||||||
}
|
|
||||||
let heights = y_s
|
|
||||||
.windows(2)
|
|
||||||
.map(|pair| pair[1] - pair[0])
|
|
||||||
.chain(vec![size.h - y_s.last().expect("vec was empty")])
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for (i, win) in stack.enumerate() {
|
|
||||||
win.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
Point::from((size.w / 2 + loc.x, y_s[i] + loc.y)),
|
|
||||||
Size::from((size.w / 2, i32::max(heights[i], 40))),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dwindle(windows: Vec<WindowElement>, rect: Rectangle<i32, Logical>) {
|
|
||||||
let size = rect.size;
|
|
||||||
let loc = rect.loc;
|
|
||||||
|
|
||||||
let mut iter = windows.windows(2).peekable();
|
|
||||||
|
|
||||||
if iter.peek().is_none() {
|
|
||||||
if let Some(window) = windows.first() {
|
|
||||||
window.change_geometry(Rectangle::from_loc_and_size(loc, size));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut win1_size = size;
|
|
||||||
let mut win1_loc = loc;
|
|
||||||
for (i, wins) in iter.enumerate() {
|
|
||||||
let win1 = &wins[0];
|
|
||||||
let win2 = &wins[1];
|
|
||||||
|
|
||||||
enum Slice {
|
|
||||||
Right,
|
|
||||||
Below,
|
|
||||||
}
|
|
||||||
|
|
||||||
let slice = if i % 2 == 0 { Slice::Right } else { Slice::Below };
|
|
||||||
|
|
||||||
match slice {
|
|
||||||
Slice::Right => {
|
|
||||||
let width_partition = win1_size.w / 2;
|
|
||||||
|
|
||||||
win1.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
win1_loc,
|
|
||||||
Size::from((win1_size.w - width_partition, i32::max(win1_size.h, 40))),
|
|
||||||
));
|
|
||||||
|
|
||||||
win1_loc = (win1_loc.x + (win1_size.w - width_partition), win1_loc.y).into();
|
|
||||||
win1_size = (width_partition, i32::max(win1_size.h, 40)).into();
|
|
||||||
|
|
||||||
win2.change_geometry(Rectangle::from_loc_and_size(win1_loc, win1_size));
|
|
||||||
}
|
|
||||||
Slice::Below => {
|
|
||||||
let height_partition = win1_size.h / 2;
|
|
||||||
|
|
||||||
win1.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
win1_loc,
|
|
||||||
Size::from((win1_size.w, i32::max(win1_size.h - height_partition, 40))),
|
|
||||||
));
|
|
||||||
|
|
||||||
win1_loc = (win1_loc.x, win1_loc.y + (win1_size.h - height_partition)).into();
|
|
||||||
win1_size = (win1_size.w, i32::max(height_partition, 40)).into();
|
|
||||||
|
|
||||||
win2.change_geometry(Rectangle::from_loc_and_size(win1_loc, win1_size));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spiral(windows: Vec<WindowElement>, rect: Rectangle<i32, Logical>) {
|
|
||||||
let size = rect.size;
|
|
||||||
let loc = rect.loc;
|
|
||||||
|
|
||||||
let mut window_pairs = windows.windows(2).peekable();
|
|
||||||
|
|
||||||
if window_pairs.peek().is_none() {
|
|
||||||
if let Some(window) = windows.first() {
|
|
||||||
window.change_geometry(Rectangle::from_loc_and_size(loc, size));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut win1_loc = loc;
|
|
||||||
let mut win1_size = size;
|
|
||||||
|
|
||||||
for (i, wins) in window_pairs.enumerate() {
|
|
||||||
let win1 = &wins[0];
|
|
||||||
let win2 = &wins[1];
|
|
||||||
|
|
||||||
enum Slice {
|
|
||||||
Above,
|
|
||||||
Below,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
let slice = match i % 4 {
|
|
||||||
0 => Slice::Right,
|
|
||||||
1 => Slice::Below,
|
|
||||||
2 => Slice::Left,
|
|
||||||
3 => Slice::Above,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match slice {
|
|
||||||
Slice::Above => {
|
|
||||||
let height_partition = win1_size.h / 2;
|
|
||||||
|
|
||||||
win1.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
Point::from((win1_loc.x, win1_loc.y + height_partition)),
|
|
||||||
Size::from((win1_size.w, i32::max(win1_size.h - height_partition, 40))),
|
|
||||||
));
|
|
||||||
|
|
||||||
win1_size = (win1_size.w, i32::max(height_partition, 40)).into();
|
|
||||||
win2.change_geometry(Rectangle::from_loc_and_size(win1_loc, win1_size));
|
|
||||||
}
|
|
||||||
Slice::Below => {
|
|
||||||
let height_partition = win1_size.h / 2;
|
|
||||||
|
|
||||||
win1.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
win1_loc,
|
|
||||||
Size::from((win1_size.w, win1_size.h - i32::max(height_partition, 40))),
|
|
||||||
));
|
|
||||||
|
|
||||||
win1_loc = (win1_loc.x, win1_loc.y + (win1_size.h - height_partition)).into();
|
|
||||||
win1_size = (win1_size.w, i32::max(height_partition, 40)).into();
|
|
||||||
win2.change_geometry(Rectangle::from_loc_and_size(win1_loc, win1_size));
|
|
||||||
}
|
|
||||||
Slice::Left => {
|
|
||||||
let width_partition = win1_size.w / 2;
|
|
||||||
|
|
||||||
win1.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
Point::from((win1_loc.x + width_partition, win1_loc.y)),
|
|
||||||
Size::from((win1_size.w - width_partition, i32::max(win1_size.h, 40))),
|
|
||||||
));
|
|
||||||
|
|
||||||
win1_size = (width_partition, i32::max(win1_size.h, 40)).into();
|
|
||||||
win2.change_geometry(Rectangle::from_loc_and_size(win1_loc, win1_size));
|
|
||||||
}
|
|
||||||
Slice::Right => {
|
|
||||||
let width_partition = win1_size.w / 2;
|
|
||||||
|
|
||||||
win1.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
win1_loc,
|
|
||||||
Size::from((win1_size.w - width_partition, i32::max(win1_size.h, 40))),
|
|
||||||
));
|
|
||||||
|
|
||||||
win1_loc = (win1_loc.x + (win1_size.w - width_partition), win1_loc.y).into();
|
|
||||||
win1_size = (width_partition, i32::max(win1_size.h, 40)).into();
|
|
||||||
win2.change_geometry(Rectangle::from_loc_and_size(win1_loc, win1_size));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn corner(layout: &Layout, windows: Vec<WindowElement>, rect: Rectangle<i32, Logical>) {
|
|
||||||
let size = rect.size;
|
|
||||||
let loc = rect.loc;
|
|
||||||
|
|
||||||
match windows.len() {
|
|
||||||
0 => (),
|
|
||||||
1 => {
|
|
||||||
windows[0].change_geometry(rect);
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
windows[0].change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
loc,
|
|
||||||
Size::from((size.w / 2, size.h)),
|
|
||||||
));
|
|
||||||
|
|
||||||
windows[1].change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
Point::from((loc.x + size.w / 2, loc.y)),
|
|
||||||
Size::from((size.w / 2, size.h)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let mut windows = windows.into_iter();
|
|
||||||
let Some(corner) = windows.next() else { unreachable!() };
|
|
||||||
|
|
||||||
let mut horiz_stack = Vec::<WindowElement>::new();
|
|
||||||
let mut vert_stack = Vec::<WindowElement>::new();
|
|
||||||
|
|
||||||
for (i, win) in windows.enumerate() {
|
|
||||||
if i % 2 == 0 {
|
|
||||||
horiz_stack.push(win);
|
|
||||||
} else {
|
|
||||||
vert_stack.push(win);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let div_factor = 2;
|
|
||||||
|
|
||||||
corner.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
Point::from(match layout {
|
|
||||||
Layout::CornerTopLeft => (loc.x, loc.y),
|
|
||||||
Layout::CornerTopRight => (loc.x + size.w - size.w / div_factor, loc.y),
|
|
||||||
Layout::CornerBottomLeft => (loc.x, loc.y + size.h - size.h / div_factor),
|
|
||||||
Layout::CornerBottomRight => (
|
|
||||||
loc.x + size.w - size.w / div_factor,
|
|
||||||
loc.y + size.h - size.h / div_factor,
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}),
|
|
||||||
Size::from((size.w / div_factor, size.h / div_factor)),
|
|
||||||
));
|
|
||||||
|
|
||||||
let vert_stack_count = vert_stack.len();
|
|
||||||
|
|
||||||
let height = size.h as f32 / vert_stack_count as f32;
|
|
||||||
let mut y_s = vec![];
|
|
||||||
for i in 0..vert_stack_count {
|
|
||||||
y_s.push((i as f32 * height).round() as i32);
|
|
||||||
}
|
|
||||||
let heights = y_s
|
|
||||||
.windows(2)
|
|
||||||
.map(|pair| pair[1] - pair[0])
|
|
||||||
.chain(vec![size.h - y_s.last().expect("vec was empty")])
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for (i, win) in vert_stack.iter().enumerate() {
|
|
||||||
win.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
Point::from((
|
|
||||||
match layout {
|
|
||||||
Layout::CornerTopLeft | Layout::CornerBottomLeft => size.w / 2 + loc.x,
|
|
||||||
Layout::CornerTopRight | Layout::CornerBottomRight => loc.x,
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
y_s[i] + loc.y,
|
|
||||||
)),
|
|
||||||
Size::from((size.w / 2, i32::max(heights[i], 40))),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let horiz_stack_count = horiz_stack.len();
|
|
||||||
|
|
||||||
let width = size.w as f32 / 2.0 / horiz_stack_count as f32;
|
|
||||||
let mut x_s = vec![];
|
|
||||||
for i in 0..horiz_stack_count {
|
|
||||||
x_s.push((i as f32 * width).round() as i32);
|
|
||||||
}
|
|
||||||
let widths = x_s
|
|
||||||
.windows(2)
|
|
||||||
.map(|pair| pair[1] - pair[0])
|
|
||||||
.chain(vec![size.w / 2 - x_s.last().expect("vec was empty")])
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for (i, win) in horiz_stack.iter().enumerate() {
|
|
||||||
win.change_geometry(Rectangle::from_loc_and_size(
|
|
||||||
Point::from(match layout {
|
|
||||||
Layout::CornerTopLeft => (x_s[i] + loc.x, loc.y + size.h / 2),
|
|
||||||
Layout::CornerTopRight => (x_s[i] + loc.x + size.w / 2, loc.y + size.h / 2),
|
|
||||||
Layout::CornerBottomLeft => (x_s[i] + loc.x, loc.y),
|
|
||||||
Layout::CornerBottomRight => (x_s[i] + loc.x + size.w / 2, loc.y),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}),
|
|
||||||
Size::from((i32::max(widths[i], 1), size.h / 2)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
/// Swaps two windows in the main window vec and updates all windows.
|
|
||||||
pub fn swap_window_positions(&mut self, win1: &WindowElement, win2: &WindowElement) {
|
|
||||||
let win1_index = self.windows.iter().position(|win| win == win1);
|
|
||||||
let win2_index = self.windows.iter().position(|win| win == win2);
|
|
||||||
|
|
||||||
if let (Some(first), Some(second)) = (win1_index, win2_index) {
|
|
||||||
self.windows.swap(first, second);
|
|
||||||
if let Some(output) = win1.output(self) {
|
|
||||||
self.update_windows(&output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New layout system stuff
|
|
||||||
|
|
||||||
/// A monotonically increasing identifier for layout requests.
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct LayoutRequestId(pub u32);
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct LayoutState {
|
|
||||||
pub layout_request_sender: Option<UnboundedSender<Result<LayoutResponse, Status>>>,
|
|
||||||
id_maps: HashMap<Output, LayoutRequestId>,
|
|
||||||
pending_requests: HashMap<Output, Vec<(LayoutRequestId, Vec<WindowElement>)>>,
|
|
||||||
old_requests: HashMap<Output, HashSet<LayoutRequestId>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn layout_request(&mut self, output: Output, windows: Vec<WindowElement>) {
|
|
||||||
let Some(sender) = self.layout_state.layout_request_sender.as_ref() else {
|
|
||||||
error!("Layout requested but no client has connected to the layout service");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some((output_width, output_height)) = self
|
let Some((output_width, output_height)) = self
|
||||||
.space
|
.space
|
||||||
.output_geometry(&output)
|
.output_geometry(output)
|
||||||
.map(|geo| (geo.size.w, geo.size.h))
|
.map(|geo| (geo.size.w, geo.size.h))
|
||||||
else {
|
else {
|
||||||
error!("Called `output_geometry` on an unmapped output");
|
error!("Called `output_geometry` on an unmapped output");
|
||||||
|
@ -706,7 +283,7 @@ impl State {
|
||||||
.map(|geo| {
|
.map(|geo| {
|
||||||
Some(Rectangle::<i32, Logical>::from_loc_and_size(
|
Some(Rectangle::<i32, Logical>::from_loc_and_size(
|
||||||
(geo.x?, geo.y?),
|
(geo.x?, geo.y?),
|
||||||
(geo.width?, geo.height?),
|
(i32::max(geo.width?, 1), i32::max(geo.height?, 1)),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect::<Option<Vec<_>>>();
|
.collect::<Option<Vec<_>>>();
|
||||||
|
|
16
src/tag.rs
16
src/tag.rs
|
@ -9,10 +9,7 @@ use std::{
|
||||||
|
|
||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
|
|
||||||
use crate::{
|
use crate::state::{State, WithState};
|
||||||
layout::Layout,
|
|
||||||
state::{State, WithState},
|
|
||||||
};
|
|
||||||
|
|
||||||
static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
|
@ -52,8 +49,6 @@ struct TagInner {
|
||||||
name: String,
|
name: String,
|
||||||
/// Whether this tag is active or not.
|
/// Whether this tag is active or not.
|
||||||
active: bool,
|
active: bool,
|
||||||
/// What layout this tag has.
|
|
||||||
layout: Layout,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for TagInner {
|
impl PartialEq for TagInner {
|
||||||
|
@ -88,14 +83,6 @@ impl Tag {
|
||||||
pub fn set_active(&self, active: bool) {
|
pub fn set_active(&self, active: bool) {
|
||||||
self.0.borrow_mut().active = active;
|
self.0.borrow_mut().active = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout(&self) -> Layout {
|
|
||||||
self.0.borrow().layout
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_layout(&self, layout: Layout) {
|
|
||||||
self.0.borrow_mut().layout = layout;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
|
@ -104,7 +91,6 @@ impl Tag {
|
||||||
id: TagId::next(),
|
id: TagId::next(),
|
||||||
name,
|
name,
|
||||||
active: false,
|
active: false,
|
||||||
layout: Layout::MasterStack, // TODO: get from config
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue