mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-13 08:01:05 +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 --
|
||||
--------------------
|
||||
|
||||
local layout_handler = Layout.new_handler({
|
||||
local layout_manager = Layout.new_cycling_manager({
|
||||
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.
|
||||
Process.spawn_once(terminal)
|
||||
|
|
|
@ -38,48 +38,79 @@ end
|
|||
---@field output_width 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 }[]
|
||||
|
||||
---@class Builtin.MasterStack : Builtin
|
||||
---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.
|
||||
---@class Builtin.MasterStack : LayoutGenerator
|
||||
---@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
|
||||
---The side the master window(s) will be on.
|
||||
---
|
||||
---Defaults to `"left"`.
|
||||
---@field master_side "left"|"right"|"top"|"bottom"
|
||||
---How many windows the master side will have.
|
||||
---
|
||||
---Defaults to 1.
|
||||
---@field master_count integer
|
||||
|
||||
---@class Builtin.Dwindle : LayoutGenerator
|
||||
---@field gaps integer | { inner: integer, outer: integer }
|
||||
---@field split_factors table<integer, number>
|
||||
|
||||
local builtins = {
|
||||
---@type Builtin.MasterStack
|
||||
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,
|
||||
---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,
|
||||
---The side the master window(s) will be on.
|
||||
---
|
||||
---Defaults to `"left"`.
|
||||
master_side = "left",
|
||||
---How many windows the master side will have.
|
||||
---
|
||||
---Defaults to 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
|
||||
|
@ -241,20 +272,137 @@ function builtins.master_stack:layout(args)
|
|||
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
|
||||
local layout = {
|
||||
builtins = builtins,
|
||||
}
|
||||
|
||||
---@param handler LayoutHandler
|
||||
function layout.set_handler(handler)
|
||||
---@param manager LayoutManager
|
||||
function layout.set_manager(manager)
|
||||
client.bidirectional_streaming_request(
|
||||
build_grpc_request_params("Layout", {
|
||||
layout = {},
|
||||
}),
|
||||
function(response, stream)
|
||||
local request_id = response.request_id
|
||||
local index = handler.index
|
||||
|
||||
---@diagnostic disable-next-line: invisible
|
||||
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,
|
||||
}
|
||||
|
||||
local geos = handler.layouts[index]:layout(args)
|
||||
local geos = manager:get_active(args):layout(args)
|
||||
|
||||
local body = protobuf.encode(".pinnacle.layout.v0alpha1.LayoutRequest", {
|
||||
geometries = {
|
||||
|
@ -289,33 +437,81 @@ function layout.set_handler(handler)
|
|||
)
|
||||
end
|
||||
|
||||
---@class LayoutHandlerModule
|
||||
local layout_handler = {}
|
||||
---@class LayoutManager
|
||||
---@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 layouts { layout: fun(self: self, args: LayoutArgs): { x: integer, y: integer, width: integer, height: integer }[] }[]
|
||||
local LayoutHandler = {}
|
||||
local CyclingLayoutManager = {
|
||||
---@type table<integer, integer>
|
||||
tag_indices = {},
|
||||
}
|
||||
|
||||
---@param layouts { layout: fun(self: self, args: LayoutArgs): { x: integer, y: integer, width: integer, height: integer }[] }[]
|
||||
---@return LayoutHandler
|
||||
function layout_handler.new(layouts)
|
||||
---@type LayoutHandler
|
||||
---@param args LayoutArgs
|
||||
---@return LayoutGenerator
|
||||
function CyclingLayoutManager:get_active(args)
|
||||
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 = {
|
||||
index = 1,
|
||||
layouts = layouts,
|
||||
}
|
||||
|
||||
setmetatable(self, { __index = LayoutHandler })
|
||||
setmetatable(self, { __index = CyclingLayoutManager })
|
||||
|
||||
return self
|
||||
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
|
||||
|
|
|
@ -191,12 +191,6 @@ function rectangle.new(x, y, width, height)
|
|||
return self
|
||||
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
|
||||
|
||||
return util
|
||||
|
|
47
src/api.rs
47
src/api.rs
|
@ -703,7 +703,7 @@ impl tag_service_server::TagService for TagService {
|
|||
return;
|
||||
};
|
||||
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.update_focus(&output);
|
||||
state.schedule_render(&output);
|
||||
})
|
||||
|
@ -730,7 +730,7 @@ impl tag_service_server::TagService for TagService {
|
|||
tag.set_active(true);
|
||||
});
|
||||
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.update_focus(&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);
|
||||
}
|
||||
|
||||
|
@ -831,40 +831,13 @@ impl tag_service_server::TagService for TagService {
|
|||
.await
|
||||
}
|
||||
|
||||
async fn set_layout(&self, request: Request<SetLayoutRequest>) -> Result<Response<()>, Status> {
|
||||
let request = request.into_inner();
|
||||
async fn set_layout(
|
||||
&self,
|
||||
_request: Request<SetLayoutRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
warn!("Tag.set_layout has been deprecated");
|
||||
|
||||
let tag_id = TagId(
|
||||
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
|
||||
run_unary_no_response(&self.sender, move |_state| {}).await
|
||||
}
|
||||
|
||||
async fn get(
|
||||
|
@ -975,7 +948,7 @@ impl output_service_server::OutputService for OutputService {
|
|||
output.change_current_state(None, None, None, Some(loc));
|
||||
state.space.map_output(&output, loc);
|
||||
tracing::debug!("Mapping output {} to {loc:?}", output.name());
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ impl layout_service_server::LayoutService for LayoutService {
|
|||
if let Some(body) = request.body {
|
||||
match body {
|
||||
layout_request::Body::Geometries(geos) => {
|
||||
// dbg!(&geos);
|
||||
if let Err(err) = state.apply_layout(geos) {
|
||||
// TODO: send a Status and handle the error client side
|
||||
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);
|
||||
},
|
||||
)
|
||||
|
|
|
@ -114,7 +114,7 @@ impl window_service_server::WindowService for WindowService {
|
|||
});
|
||||
|
||||
for output in state.space.outputs_for_element(&window) {
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.schedule_render(&output);
|
||||
}
|
||||
})
|
||||
|
@ -163,7 +163,7 @@ impl window_service_server::WindowService for WindowService {
|
|||
return;
|
||||
};
|
||||
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.schedule_render(&output);
|
||||
})
|
||||
.await
|
||||
|
@ -211,7 +211,7 @@ impl window_service_server::WindowService for WindowService {
|
|||
return;
|
||||
};
|
||||
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.schedule_render(&output);
|
||||
})
|
||||
.await
|
||||
|
@ -259,7 +259,7 @@ impl window_service_server::WindowService for WindowService {
|
|||
return;
|
||||
};
|
||||
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.schedule_render(&output);
|
||||
})
|
||||
.await
|
||||
|
@ -349,7 +349,7 @@ impl window_service_server::WindowService for WindowService {
|
|||
}
|
||||
}
|
||||
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.schedule_render(&output);
|
||||
})
|
||||
.await
|
||||
|
@ -380,7 +380,7 @@ impl window_service_server::WindowService for WindowService {
|
|||
state.tags = vec![tag.clone()];
|
||||
});
|
||||
let Some(output) = tag.output(state) else { return };
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.schedule_render(&output);
|
||||
})
|
||||
.await
|
||||
|
@ -431,7 +431,7 @@ impl window_service_server::WindowService for WindowService {
|
|||
}
|
||||
|
||||
let Some(output) = tag.output(state) else { return };
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
state.schedule_render(&output);
|
||||
})
|
||||
.await
|
||||
|
|
|
@ -233,7 +233,7 @@ pub fn setup_winit(
|
|||
None,
|
||||
);
|
||||
layer_map_for_output(&output).arrange();
|
||||
state.update_windows(&output);
|
||||
state.request_layout(&output);
|
||||
}
|
||||
WinitEvent::Focus(_) => {}
|
||||
WinitEvent::Input(input_evt) => {
|
||||
|
|
|
@ -161,7 +161,7 @@ impl CompositorHandler for State {
|
|||
self.apply_window_rules(&new_window);
|
||||
|
||||
if let Some(focused_output) = self.focused_output().cloned() {
|
||||
self.update_windows(&focused_output);
|
||||
self.request_layout(&focused_output);
|
||||
new_window.send_frame(
|
||||
&focused_output,
|
||||
self.clock.now(),
|
||||
|
@ -514,7 +514,7 @@ impl WlrLayerShellHandler for State {
|
|||
drop(map); // wow i really love refcells haha
|
||||
|
||||
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 {
|
||||
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) {
|
||||
self.update_windows(&output);
|
||||
self.request_layout(&output);
|
||||
let focus = self
|
||||
.focused_window(&output)
|
||||
.map(KeyboardFocusTarget::Window);
|
||||
|
@ -766,7 +766,7 @@ impl XdgShellHandler for State {
|
|||
}
|
||||
|
||||
let Some(output) = window.output(self) else { return };
|
||||
self.update_windows(&output);
|
||||
self.request_layout(&output);
|
||||
}
|
||||
|
||||
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
|
||||
|
@ -779,7 +779,7 @@ impl XdgShellHandler for State {
|
|||
}
|
||||
|
||||
let Some(output) = window.output(self) else { return };
|
||||
self.update_windows(&output);
|
||||
self.request_layout(&output);
|
||||
}
|
||||
|
||||
fn minimize_request(&mut self, _surface: ToplevelSurface) {
|
||||
|
|
|
@ -103,7 +103,7 @@ impl XwmHandler for State {
|
|||
|
||||
if let Some(output) = window.output(self) {
|
||||
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| {
|
||||
|
@ -166,7 +166,7 @@ impl XwmHandler for State {
|
|||
self.space.unmap_elem(&win);
|
||||
|
||||
if let Some(output) = win.output(self) {
|
||||
self.update_windows(&output);
|
||||
self.request_layout(&output);
|
||||
|
||||
let focus = self
|
||||
.focused_window(&output)
|
||||
|
@ -227,7 +227,7 @@ impl XwmHandler for State {
|
|||
.retain(|elem| win.wl_surface() != elem.wl_surface());
|
||||
|
||||
if let Some(output) = win.output(self) {
|
||||
self.update_windows(&output);
|
||||
self.request_layout(&output);
|
||||
|
||||
let focus = self
|
||||
.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::{
|
||||
desktop::{layer_map_for_output, WindowSurface},
|
||||
output::Output,
|
||||
utils::{Logical, Point, Rectangle, Serial, Size},
|
||||
utils::{Logical, Point, Rectangle, Serial},
|
||||
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
@ -23,39 +23,6 @@ use crate::{
|
|||
};
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
output: &Output,
|
||||
|
@ -163,10 +130,36 @@ impl State {
|
|||
self.fixup_z_layering();
|
||||
}
|
||||
|
||||
pub fn update_windows(&mut self, output: &Output) {
|
||||
let Some(layout) =
|
||||
output.with_state(|state| state.focused_tags().next().map(|tag| tag.layout()))
|
||||
else {
|
||||
/// 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.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;
|
||||
};
|
||||
|
||||
|
@ -182,7 +175,7 @@ impl State {
|
|||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let tiled_windows = windows_on_foc_tags
|
||||
let windows = windows_on_foc_tags
|
||||
.iter()
|
||||
.filter(|win| {
|
||||
win.with_state(|state| {
|
||||
|
@ -192,425 +185,9 @@ impl State {
|
|||
.cloned()
|
||||
.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
|
||||
.space
|
||||
.output_geometry(&output)
|
||||
.output_geometry(output)
|
||||
.map(|geo| (geo.size.w, geo.size.h))
|
||||
else {
|
||||
error!("Called `output_geometry` on an unmapped output");
|
||||
|
@ -706,7 +283,7 @@ impl State {
|
|||
.map(|geo| {
|
||||
Some(Rectangle::<i32, Logical>::from_loc_and_size(
|
||||
(geo.x?, geo.y?),
|
||||
(geo.width?, geo.height?),
|
||||
(i32::max(geo.width?, 1), i32::max(geo.height?, 1)),
|
||||
))
|
||||
})
|
||||
.collect::<Option<Vec<_>>>();
|
||||
|
|
16
src/tag.rs
16
src/tag.rs
|
@ -9,10 +9,7 @@ use std::{
|
|||
|
||||
use smithay::output::Output;
|
||||
|
||||
use crate::{
|
||||
layout::Layout,
|
||||
state::{State, WithState},
|
||||
};
|
||||
use crate::state::{State, WithState};
|
||||
|
||||
static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
|
@ -52,8 +49,6 @@ struct TagInner {
|
|||
name: String,
|
||||
/// Whether this tag is active or not.
|
||||
active: bool,
|
||||
/// What layout this tag has.
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
impl PartialEq for TagInner {
|
||||
|
@ -88,14 +83,6 @@ impl Tag {
|
|||
pub fn set_active(&self, active: bool) {
|
||||
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 {
|
||||
|
@ -104,7 +91,6 @@ impl Tag {
|
|||
id: TagId::next(),
|
||||
name,
|
||||
active: false,
|
||||
layout: Layout::MasterStack, // TODO: get from config
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue