Add layout cycling, dwindle, remove old layout stuff

This commit is contained in:
Ottatop 2024-03-14 19:21:51 -05:00
parent b3ba9f9393
commit e1f2706428
12 changed files with 347 additions and 585 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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);
},
)

View file

@ -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

View file

@ -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) => {

View file

@ -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);
});
}
}

View file

@ -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) {

View file

@ -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)

View file

@ -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<_>>>();

View file

@ -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
})))
}