mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Add Rust layouts
This commit is contained in:
parent
ab2b3ee13b
commit
a98777c11e
6 changed files with 1154 additions and 35 deletions
|
@ -283,11 +283,11 @@ function builtins.master_stack:layout(args)
|
||||||
|
|
||||||
if self.master_side == "left" or self.master_side == "right" then
|
if self.master_side == "left" or self.master_side == "right" then
|
||||||
coord = master_rect.y
|
coord = master_rect.y
|
||||||
len = master_rect.height
|
len = master_rect.height // (master_slice_count + 1)
|
||||||
axis = "horizontal"
|
axis = "horizontal"
|
||||||
else
|
else
|
||||||
coord = master_rect.x
|
coord = master_rect.x
|
||||||
len = master_rect.width
|
len = master_rect.width // (master_slice_count + 1)
|
||||||
axis = "vertical"
|
axis = "vertical"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -680,23 +680,21 @@ function builtins.fair:layout(args)
|
||||||
table.insert(geos, rect)
|
table.insert(geos, rect)
|
||||||
elseif win_count == 2 then
|
elseif win_count == 2 then
|
||||||
local len
|
local len
|
||||||
|
local coord
|
||||||
if self.direction == "vertical" then
|
if self.direction == "vertical" then
|
||||||
len = rect.width
|
len = rect.width
|
||||||
|
coord = rect.x
|
||||||
else
|
else
|
||||||
len = rect.height
|
len = rect.height
|
||||||
|
coord = rect.y
|
||||||
end
|
end
|
||||||
-- Two windows is special cased to create a new line rather than increase to 2 in a line
|
-- Two windows is special cased to create a new line rather than increase to 2 in a line
|
||||||
local rect1, rect2 = rect:split_at(self.direction, len // 2 - gaps // 2, gaps)
|
local rect1, rect2 = rect:split_at(self.direction, coord + len // 2 - gaps // 2, gaps)
|
||||||
if rect1 and rect2 then
|
if rect1 and rect2 then
|
||||||
table.insert(geos, rect1)
|
table.insert(geos, rect1)
|
||||||
table.insert(geos, rect2)
|
table.insert(geos, rect2)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- 3 / 1
|
|
||||||
-- 7 / 2
|
|
||||||
-- 13 / 3
|
|
||||||
-- 21 / 4
|
|
||||||
|
|
||||||
local line_count = math.floor(math.sqrt(win_count) + 0.5)
|
local line_count = math.floor(math.sqrt(win_count) + 0.5)
|
||||||
local wins_per_line = {}
|
local wins_per_line = {}
|
||||||
local max_per_line = line_count
|
local max_per_line = line_count
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use pinnacle_api::layout::{
|
||||||
|
CornerLayout, DwindleLayout, FairLayout, LayoutManager, MasterStackLayout, SpiralLayout,
|
||||||
|
};
|
||||||
use pinnacle_api::signal::WindowSignal;
|
use pinnacle_api::signal::WindowSignal;
|
||||||
use pinnacle_api::xkbcommon::xkb::Keysym;
|
use pinnacle_api::xkbcommon::xkb::Keysym;
|
||||||
use pinnacle_api::{
|
use pinnacle_api::{
|
||||||
|
@ -15,6 +18,7 @@ async fn main() {
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
tag,
|
tag,
|
||||||
|
layout,
|
||||||
} = modules;
|
} = modules;
|
||||||
|
|
||||||
let mod_key = Mod::Ctrl;
|
let mod_key = Mod::Ctrl;
|
||||||
|
@ -78,6 +82,54 @@ async fn main() {
|
||||||
// You can define window rules to get windows to open with desired properties.
|
// You can define window rules to get windows to open with desired properties.
|
||||||
// See `pinnacle_api::window::rules` in the docs for more information.
|
// See `pinnacle_api::window::rules` in the docs for more information.
|
||||||
|
|
||||||
|
// Layouts
|
||||||
|
|
||||||
|
let master_stack = Box::<MasterStackLayout>::default();
|
||||||
|
let dwindle = Box::<DwindleLayout>::default();
|
||||||
|
let spiral = Box::<SpiralLayout>::default();
|
||||||
|
let corner = Box::<CornerLayout>::default();
|
||||||
|
let fair = Box::<FairLayout>::default();
|
||||||
|
|
||||||
|
let layout_requester = layout.set_manager(layout.new_cycling_manager([
|
||||||
|
master_stack as _,
|
||||||
|
dwindle as _,
|
||||||
|
spiral as _,
|
||||||
|
corner as _,
|
||||||
|
fair as _,
|
||||||
|
]));
|
||||||
|
|
||||||
|
let mut layout_requester_clone = layout_requester.clone();
|
||||||
|
|
||||||
|
// `mod_key + space` cycles to the next layout
|
||||||
|
input.keybind([mod_key], Keysym::space, move || {
|
||||||
|
let Some(focused_op) = output.get_focused() else { return };
|
||||||
|
let Some(first_active_tag) = focused_op
|
||||||
|
.tags()
|
||||||
|
.into_iter()
|
||||||
|
.find(|tg| tg.active().unwrap_or(false))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout_requester.cycle_layout_forward(&first_active_tag);
|
||||||
|
layout_requester.request_layout_on_output(&focused_op);
|
||||||
|
});
|
||||||
|
|
||||||
|
// `mod_key + shift + space` cycles to the previous layout
|
||||||
|
input.keybind([mod_key, Mod::Shift], Keysym::space, move || {
|
||||||
|
let Some(focused_op) = output.get_focused() else { return };
|
||||||
|
let Some(first_active_tag) = focused_op
|
||||||
|
.tags()
|
||||||
|
.into_iter()
|
||||||
|
.find(|tg| tg.active().unwrap_or(false))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout_requester_clone.cycle_layout_backward(&first_active_tag);
|
||||||
|
layout_requester_clone.request_layout_on_output(&focused_op);
|
||||||
|
});
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
|
|
||||||
let tag_names = ["1", "2", "3", "4", "5"];
|
let tag_names = ["1", "2", "3", "4", "5"];
|
||||||
|
@ -92,30 +144,6 @@ async fn main() {
|
||||||
|
|
||||||
process.spawn_once([terminal]);
|
process.spawn_once([terminal]);
|
||||||
|
|
||||||
// Create a layout cycler to cycle through the given layouts
|
|
||||||
let LayoutCycler {
|
|
||||||
prev: layout_prev,
|
|
||||||
next: layout_next,
|
|
||||||
} = tag.new_layout_cycler([
|
|
||||||
Layout::MasterStack,
|
|
||||||
Layout::Dwindle,
|
|
||||||
Layout::Spiral,
|
|
||||||
Layout::CornerTopLeft,
|
|
||||||
Layout::CornerTopRight,
|
|
||||||
Layout::CornerBottomLeft,
|
|
||||||
Layout::CornerBottomRight,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// `mod_key + space` cycles to the next layout
|
|
||||||
input.keybind([mod_key], Keysym::space, move || {
|
|
||||||
layout_next(None);
|
|
||||||
});
|
|
||||||
|
|
||||||
// `mod_key + shift + space` cycles to the previous layout
|
|
||||||
input.keybind([mod_key, Mod::Shift], Keysym::space, move || {
|
|
||||||
layout_prev(None);
|
|
||||||
});
|
|
||||||
|
|
||||||
for tag_name in tag_names {
|
for tag_name in tag_names {
|
||||||
// `mod_key + 1-5` switches to tag "1" to "5"
|
// `mod_key + 1-5` switches to tag "1" to "5"
|
||||||
input.keybind([mod_key], tag_name, move || {
|
input.keybind([mod_key], tag_name, move || {
|
||||||
|
|
995
api/rust/src/layout.rs
Normal file
995
api/rust/src/layout.rs
Normal file
|
@ -0,0 +1,995 @@
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
//! Layout management.
|
||||||
|
//!
|
||||||
|
//! TODO:
|
||||||
|
|
||||||
|
#![allow(missing_docs)] // TODO:
|
||||||
|
|
||||||
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
use pinnacle_api_defs::pinnacle::layout::v0alpha1::{
|
||||||
|
layout_request::{Body, ExplicitLayout, Geometries},
|
||||||
|
layout_service_client::LayoutServiceClient,
|
||||||
|
LayoutRequest,
|
||||||
|
};
|
||||||
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
block_on_tokio,
|
||||||
|
output::OutputHandle,
|
||||||
|
tag::TagHandle,
|
||||||
|
util::{Axis, Geometry},
|
||||||
|
window::WindowHandle,
|
||||||
|
OUTPUT, TAG, WINDOW,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A struct that allows you to add and remove tags and get [`TagHandle`]s.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Layout {
|
||||||
|
layout_client: LayoutServiceClient<Channel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout {
|
||||||
|
pub(crate) fn new(channel: Channel) -> Self {
|
||||||
|
Self {
|
||||||
|
layout_client: LayoutServiceClient::new(channel.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_cycling_manager(
|
||||||
|
&self,
|
||||||
|
layouts: impl IntoIterator<Item = Box<dyn LayoutGenerator + Send>>,
|
||||||
|
) -> CyclingLayoutManager {
|
||||||
|
CyclingLayoutManager {
|
||||||
|
layouts: layouts.into_iter().collect(),
|
||||||
|
tag_indices: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_manager<M>(&self, manager: M) -> LayoutRequester<M>
|
||||||
|
where
|
||||||
|
M: LayoutManager + Send + 'static,
|
||||||
|
{
|
||||||
|
let (from_client, to_server) = unbounded_channel::<LayoutRequest>();
|
||||||
|
let to_server_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(to_server);
|
||||||
|
let mut from_server = block_on_tokio(self.layout_client.clone().layout(to_server_stream))
|
||||||
|
.expect("TODO")
|
||||||
|
.into_inner();
|
||||||
|
|
||||||
|
let from_client_clone = from_client.clone();
|
||||||
|
|
||||||
|
let manager = Arc::new(tokio::sync::Mutex::new(manager));
|
||||||
|
|
||||||
|
let requester = LayoutRequester {
|
||||||
|
sender: from_client_clone,
|
||||||
|
manager: manager.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let thing = async move {
|
||||||
|
while let Some(Ok(response)) = from_server.next().await {
|
||||||
|
let args = LayoutArgs {
|
||||||
|
output: OUTPUT.get().unwrap().new_handle(response.output_name()),
|
||||||
|
windows: response
|
||||||
|
.window_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(|id| WINDOW.get().unwrap().new_handle(id))
|
||||||
|
.collect(),
|
||||||
|
tags: response
|
||||||
|
.tag_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(|id| TAG.get().unwrap().new_handle(id))
|
||||||
|
.collect(),
|
||||||
|
output_width: response.output_width.unwrap_or_default(),
|
||||||
|
output_height: response.output_height.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
let geos = manager.lock().await.active_layout(&args).layout(&args);
|
||||||
|
from_client
|
||||||
|
.send(LayoutRequest {
|
||||||
|
body: Some(Body::Geometries(Geometries {
|
||||||
|
request_id: response.request_id,
|
||||||
|
output_name: response.output_name,
|
||||||
|
geometries: geos
|
||||||
|
.into_iter()
|
||||||
|
.map(|geo| pinnacle_api_defs::pinnacle::v0alpha1::Geometry {
|
||||||
|
x: Some(geo.x),
|
||||||
|
y: Some(geo.y),
|
||||||
|
width: Some(geo.width as i32),
|
||||||
|
height: Some(geo.height as i32),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::spawn(thing);
|
||||||
|
requester
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LayoutArgs {
|
||||||
|
pub output: OutputHandle,
|
||||||
|
pub windows: Vec<WindowHandle>,
|
||||||
|
pub tags: Vec<TagHandle>,
|
||||||
|
pub output_width: u32,
|
||||||
|
pub output_height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait LayoutManager {
|
||||||
|
fn active_layout(&mut self, args: &LayoutArgs) -> &dyn LayoutGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait LayoutGenerator {
|
||||||
|
fn layout(&self, args: &LayoutArgs) -> Vec<Geometry>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Gaps {
|
||||||
|
Absolute(u32),
|
||||||
|
Split { inner: u32, outer: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CyclingLayoutManager {
|
||||||
|
layouts: Vec<Box<dyn LayoutGenerator + Send>>,
|
||||||
|
tag_indices: HashMap<u32, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CyclingLayoutManager {
|
||||||
|
pub fn new(
|
||||||
|
layout: &Layout,
|
||||||
|
layouts: impl IntoIterator<Item = Box<dyn LayoutGenerator + Send>>,
|
||||||
|
) -> Self {
|
||||||
|
layout.new_cycling_manager(layouts)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_layout_forward(&mut self, tag: &TagHandle) {
|
||||||
|
let index = self.tag_indices.entry(tag.id).or_default();
|
||||||
|
*index += 1;
|
||||||
|
if *index >= self.layouts.len() {
|
||||||
|
*index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_layout_backward(&mut self, tag: &TagHandle) {
|
||||||
|
let index = self.tag_indices.entry(tag.id).or_default();
|
||||||
|
if let Some(i) = index.checked_sub(1) {
|
||||||
|
*index = i;
|
||||||
|
} else {
|
||||||
|
*index = self.layouts.len().saturating_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutManager for CyclingLayoutManager {
|
||||||
|
fn active_layout(&mut self, args: &LayoutArgs) -> &dyn LayoutGenerator {
|
||||||
|
let Some(first_tag) = args.tags.first() else {
|
||||||
|
return &NoopLayout;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.layouts
|
||||||
|
.get(*self.tag_indices.entry(first_tag.id).or_default())
|
||||||
|
.expect("no layouts in manager")
|
||||||
|
.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LayoutRequester<T> {
|
||||||
|
sender: UnboundedSender<LayoutRequest>,
|
||||||
|
pub manager: Arc<tokio::sync::Mutex<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for LayoutRequester<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
sender: self.sender.clone(),
|
||||||
|
manager: self.manager.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> LayoutRequester<T> {
|
||||||
|
pub fn request_layout(&self) {
|
||||||
|
let output_name = OUTPUT.get().unwrap().get_focused().map(|op| op.name);
|
||||||
|
self.sender
|
||||||
|
.send(LayoutRequest {
|
||||||
|
body: Some(Body::Layout(ExplicitLayout { output_name })),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_layout_on_output(&self, output: &OutputHandle) {
|
||||||
|
self.sender
|
||||||
|
.send(LayoutRequest {
|
||||||
|
body: Some(Body::Layout(ExplicitLayout {
|
||||||
|
output_name: Some(output.name.clone()),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutRequester<CyclingLayoutManager> {
|
||||||
|
pub fn cycle_layout_forward(&self, tag: &TagHandle) {
|
||||||
|
let mut lock = block_on_tokio(self.manager.lock());
|
||||||
|
lock.cycle_layout_forward(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_layout_backward(&mut self, tag: &TagHandle) {
|
||||||
|
let mut lock = block_on_tokio(self.manager.lock());
|
||||||
|
lock.cycle_layout_backward(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NoopLayout;
|
||||||
|
|
||||||
|
impl LayoutGenerator for NoopLayout {
|
||||||
|
fn layout(&self, _args: &LayoutArgs) -> Vec<Geometry> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MasterSide {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct MasterStackLayout {
|
||||||
|
pub gaps: Gaps,
|
||||||
|
pub master_factor: f32,
|
||||||
|
pub master_side: MasterSide,
|
||||||
|
pub master_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MasterStackLayout {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
gaps: Gaps::Absolute(8),
|
||||||
|
master_factor: 0.5,
|
||||||
|
master_side: MasterSide::Left,
|
||||||
|
master_count: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutGenerator for MasterStackLayout {
|
||||||
|
fn layout(&self, args: &LayoutArgs) -> Vec<Geometry> {
|
||||||
|
let win_count = args.windows.len() as u32;
|
||||||
|
|
||||||
|
if win_count == 0 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = args.output_width;
|
||||||
|
let height = args.output_height;
|
||||||
|
|
||||||
|
let mut geos = Vec::<Geometry>::new();
|
||||||
|
|
||||||
|
let (outer_gaps, inner_gaps) = match self.gaps {
|
||||||
|
Gaps::Absolute(gaps) => (gaps, None),
|
||||||
|
Gaps::Split { inner, outer } => (outer, Some(inner)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rect = Geometry {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
.split_at(Axis::Horizontal, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Horizontal, (height - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, (width - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
let master_factor = if win_count > self.master_count {
|
||||||
|
self.master_factor.clamp(0.1, 0.9)
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let gaps = match inner_gaps {
|
||||||
|
Some(_) => 0,
|
||||||
|
None => outer_gaps,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (master_rect, mut stack_rect) = match self.master_side {
|
||||||
|
MasterSide::Left => {
|
||||||
|
let (rect1, rect2) = rect.split_at(
|
||||||
|
Axis::Vertical,
|
||||||
|
(width as f32 * master_factor).floor() as i32 - gaps as i32 / 2,
|
||||||
|
gaps,
|
||||||
|
);
|
||||||
|
(Some(rect1), rect2)
|
||||||
|
}
|
||||||
|
MasterSide::Right => {
|
||||||
|
let (rect2, rect1) = rect.split_at(
|
||||||
|
Axis::Vertical,
|
||||||
|
(width as f32 * master_factor).floor() as i32 - gaps as i32 / 2,
|
||||||
|
gaps,
|
||||||
|
);
|
||||||
|
(rect1, Some(rect2))
|
||||||
|
}
|
||||||
|
MasterSide::Top => {
|
||||||
|
let (rect1, rect2) = rect.split_at(
|
||||||
|
Axis::Horizontal,
|
||||||
|
(height as f32 * master_factor).floor() as i32 - gaps as i32 / 2,
|
||||||
|
gaps,
|
||||||
|
);
|
||||||
|
(Some(rect1), rect2)
|
||||||
|
}
|
||||||
|
MasterSide::Bottom => {
|
||||||
|
let (rect2, rect1) = rect.split_at(
|
||||||
|
Axis::Horizontal,
|
||||||
|
(height as f32 * master_factor).floor() as i32 - gaps as i32 / 2,
|
||||||
|
gaps,
|
||||||
|
);
|
||||||
|
(rect1, Some(rect2))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut master_rect = master_rect.unwrap_or_else(|| stack_rect.take().unwrap());
|
||||||
|
|
||||||
|
let (master_count, stack_count) = if win_count > self.master_count {
|
||||||
|
(self.master_count, Some(win_count - self.master_count))
|
||||||
|
} else {
|
||||||
|
(win_count, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
if master_count > 1 {
|
||||||
|
let (coord, len, axis) = match self.master_side {
|
||||||
|
MasterSide::Left | MasterSide::Right => (
|
||||||
|
master_rect.y,
|
||||||
|
master_rect.height as f32 / master_count as f32,
|
||||||
|
Axis::Horizontal,
|
||||||
|
),
|
||||||
|
MasterSide::Top | MasterSide::Bottom => (
|
||||||
|
master_rect.x,
|
||||||
|
master_rect.width as f32 / master_count as f32,
|
||||||
|
Axis::Vertical,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 1..master_count {
|
||||||
|
let slice_point = coord + (len * i as f32) as i32 - gaps as i32 / 2;
|
||||||
|
let (to_push, rest) = master_rect.split_at(axis, slice_point, gaps);
|
||||||
|
geos.push(to_push);
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
master_rect = rest;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos.push(master_rect);
|
||||||
|
|
||||||
|
if let Some(stack_count) = stack_count {
|
||||||
|
let mut stack_rect = stack_rect.unwrap();
|
||||||
|
|
||||||
|
if stack_count > 1 {
|
||||||
|
let (coord, len, axis) = match self.master_side {
|
||||||
|
MasterSide::Left | MasterSide::Right => (
|
||||||
|
stack_rect.y,
|
||||||
|
stack_rect.height as f32 / stack_count as f32,
|
||||||
|
Axis::Horizontal,
|
||||||
|
),
|
||||||
|
MasterSide::Top | MasterSide::Bottom => (
|
||||||
|
stack_rect.x,
|
||||||
|
stack_rect.width as f32 / stack_count as f32,
|
||||||
|
Axis::Vertical,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 1..stack_count {
|
||||||
|
let slice_point = coord + (len * i as f32) as i32 - gaps as i32 / 2;
|
||||||
|
let (to_push, rest) = stack_rect.split_at(axis, slice_point, gaps);
|
||||||
|
geos.push(to_push);
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
stack_rect = rest;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos.push(stack_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(inner_gaps) = inner_gaps {
|
||||||
|
for geo in geos.iter_mut() {
|
||||||
|
geo.x += inner_gaps as i32;
|
||||||
|
geo.y += inner_gaps as i32;
|
||||||
|
geo.width -= inner_gaps * 2;
|
||||||
|
geo.height -= inner_gaps * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct DwindleLayout {
|
||||||
|
pub gaps: Gaps,
|
||||||
|
pub split_factors: HashMap<usize, f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DwindleLayout {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
gaps: Gaps::Absolute(8),
|
||||||
|
split_factors: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutGenerator for DwindleLayout {
|
||||||
|
fn layout(&self, args: &LayoutArgs) -> Vec<Geometry> {
|
||||||
|
let win_count = args.windows.len() as u32;
|
||||||
|
|
||||||
|
if win_count == 0 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = args.output_width;
|
||||||
|
let height = args.output_height;
|
||||||
|
|
||||||
|
let mut geos = Vec::<Geometry>::new();
|
||||||
|
|
||||||
|
let (outer_gaps, inner_gaps) = match self.gaps {
|
||||||
|
Gaps::Absolute(gaps) => (gaps, None),
|
||||||
|
Gaps::Split { inner, outer } => (outer, Some(inner)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let gaps = match inner_gaps {
|
||||||
|
Some(_) => 0,
|
||||||
|
None => outer_gaps,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rect = Geometry {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
.split_at(Axis::Horizontal, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Horizontal, (height - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, (width - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
if win_count == 1 {
|
||||||
|
geos.push(rect)
|
||||||
|
} else {
|
||||||
|
for i in 1..win_count {
|
||||||
|
let factor = self
|
||||||
|
.split_factors
|
||||||
|
.get(&(i as usize))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0.5)
|
||||||
|
.clamp(0.1, 0.9);
|
||||||
|
|
||||||
|
let (axis, mut split_coord) = if i % 2 == 1 {
|
||||||
|
(Axis::Vertical, rect.x + (rect.width as f32 * factor) as i32)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
Axis::Horizontal,
|
||||||
|
rect.y + (rect.height as f32 * factor) as i32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
split_coord -= gaps as i32 / 2;
|
||||||
|
|
||||||
|
let (to_push, rest) = rect.split_at(axis, split_coord, gaps);
|
||||||
|
|
||||||
|
geos.push(to_push);
|
||||||
|
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
rect = rest;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos.push(rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(inner_gaps) = inner_gaps {
|
||||||
|
for geo in geos.iter_mut() {
|
||||||
|
geo.x += inner_gaps as i32;
|
||||||
|
geo.y += inner_gaps as i32;
|
||||||
|
geo.width -= inner_gaps * 2;
|
||||||
|
geo.height -= inner_gaps * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct SpiralLayout {
|
||||||
|
pub gaps: Gaps,
|
||||||
|
pub split_factors: HashMap<usize, f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SpiralLayout {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
gaps: Gaps::Absolute(8),
|
||||||
|
split_factors: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutGenerator for SpiralLayout {
|
||||||
|
fn layout(&self, args: &LayoutArgs) -> Vec<Geometry> {
|
||||||
|
let win_count = args.windows.len() as u32;
|
||||||
|
|
||||||
|
if win_count == 0 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = args.output_width;
|
||||||
|
let height = args.output_height;
|
||||||
|
|
||||||
|
let mut geos = Vec::<Geometry>::new();
|
||||||
|
|
||||||
|
let (outer_gaps, inner_gaps) = match self.gaps {
|
||||||
|
Gaps::Absolute(gaps) => (gaps, None),
|
||||||
|
Gaps::Split { inner, outer } => (outer, Some(inner)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let gaps = match inner_gaps {
|
||||||
|
Some(_) => 0,
|
||||||
|
None => outer_gaps,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rect = Geometry {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
.split_at(Axis::Horizontal, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Horizontal, (height - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, (width - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
if win_count == 1 {
|
||||||
|
geos.push(rect)
|
||||||
|
} else {
|
||||||
|
for i in 1..win_count {
|
||||||
|
let factor = self
|
||||||
|
.split_factors
|
||||||
|
.get(&(i as usize))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0.5)
|
||||||
|
.clamp(0.1, 0.9);
|
||||||
|
|
||||||
|
let (axis, mut split_coord) = if i % 2 == 1 {
|
||||||
|
(Axis::Vertical, rect.x + (rect.width as f32 * factor) as i32)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
Axis::Horizontal,
|
||||||
|
rect.y + (rect.height as f32 * factor) as i32,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
split_coord -= gaps as i32 / 2;
|
||||||
|
|
||||||
|
let (to_push, rest) = if let 1 | 2 = i % 4 {
|
||||||
|
let (to_push, rest) = rect.split_at(axis, split_coord, gaps);
|
||||||
|
(Some(to_push), rest)
|
||||||
|
} else {
|
||||||
|
let (rest, to_push) = rect.split_at(axis, split_coord, gaps);
|
||||||
|
(to_push, Some(rest))
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(to_push) = to_push {
|
||||||
|
geos.push(to_push);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
rect = rest;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos.push(rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(inner_gaps) = inner_gaps {
|
||||||
|
for geo in geos.iter_mut() {
|
||||||
|
geo.x += inner_gaps as i32;
|
||||||
|
geo.y += inner_gaps as i32;
|
||||||
|
geo.width -= inner_gaps * 2;
|
||||||
|
geo.height -= inner_gaps * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum CornerLocation {
|
||||||
|
TopLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct CornerLayout {
|
||||||
|
pub gaps: Gaps,
|
||||||
|
pub corner_width_factor: f32,
|
||||||
|
pub corner_height_factor: f32,
|
||||||
|
pub corner_loc: CornerLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CornerLayout {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
gaps: Gaps::Absolute(8),
|
||||||
|
corner_width_factor: 0.5,
|
||||||
|
corner_height_factor: 0.5,
|
||||||
|
corner_loc: CornerLocation::TopLeft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutGenerator for CornerLayout {
|
||||||
|
fn layout(&self, args: &LayoutArgs) -> Vec<Geometry> {
|
||||||
|
let win_count = args.windows.len() as u32;
|
||||||
|
|
||||||
|
if win_count == 0 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = args.output_width;
|
||||||
|
let height = args.output_height;
|
||||||
|
|
||||||
|
let mut geos = Vec::<Geometry>::new();
|
||||||
|
|
||||||
|
let (outer_gaps, inner_gaps) = match self.gaps {
|
||||||
|
Gaps::Absolute(gaps) => (gaps, None),
|
||||||
|
Gaps::Split { inner, outer } => (outer, Some(inner)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let gaps = match inner_gaps {
|
||||||
|
Some(_) => 0,
|
||||||
|
None => outer_gaps,
|
||||||
|
};
|
||||||
|
|
||||||
|
let rect = Geometry {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
.split_at(Axis::Horizontal, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Horizontal, (height - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, (width - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
if win_count == 1 {
|
||||||
|
geos.push(rect)
|
||||||
|
} else {
|
||||||
|
let (mut corner_rect, vert_stack_rect) = match self.corner_loc {
|
||||||
|
CornerLocation::TopLeft | CornerLocation::BottomLeft => {
|
||||||
|
let x_slice_point = rect.x
|
||||||
|
+ (rect.width as f32 * self.corner_width_factor).round() as i32
|
||||||
|
- gaps as i32 / 2;
|
||||||
|
let (corner_rect, vert_stack_rect) =
|
||||||
|
rect.split_at(Axis::Vertical, x_slice_point, gaps);
|
||||||
|
(Some(corner_rect), vert_stack_rect)
|
||||||
|
}
|
||||||
|
CornerLocation::TopRight | CornerLocation::BottomRight => {
|
||||||
|
let x_slice_point = rect.x
|
||||||
|
+ (rect.width as f32 * (1.0 - self.corner_width_factor)).round() as i32
|
||||||
|
- gaps as i32 / 2;
|
||||||
|
let (vert_stack_rect, corner_rect) =
|
||||||
|
rect.split_at(Axis::Vertical, x_slice_point, gaps);
|
||||||
|
(corner_rect, Some(vert_stack_rect))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if win_count == 2 {
|
||||||
|
geos.extend([corner_rect, vert_stack_rect].into_iter().flatten());
|
||||||
|
} else {
|
||||||
|
let horiz_stack_rect = match self.corner_loc {
|
||||||
|
CornerLocation::TopLeft | CornerLocation::TopRight => {
|
||||||
|
let y_slice_point = rect.y
|
||||||
|
+ (rect.height as f32 * self.corner_height_factor).round() as i32
|
||||||
|
- gaps as i32 / 2;
|
||||||
|
|
||||||
|
corner_rect.and_then(|corner| {
|
||||||
|
let (corner, horiz) =
|
||||||
|
corner.split_at(Axis::Horizontal, y_slice_point, gaps);
|
||||||
|
corner_rect = Some(corner);
|
||||||
|
horiz
|
||||||
|
})
|
||||||
|
}
|
||||||
|
CornerLocation::BottomLeft | CornerLocation::BottomRight => {
|
||||||
|
let y_slice_point = rect.y
|
||||||
|
+ (rect.height as f32 * (1.0 - self.corner_height_factor)).round()
|
||||||
|
as i32
|
||||||
|
- gaps as i32 / 2;
|
||||||
|
|
||||||
|
corner_rect.map(|corner| {
|
||||||
|
let (horiz, corner) =
|
||||||
|
corner.split_at(Axis::Horizontal, y_slice_point, gaps);
|
||||||
|
corner_rect = corner;
|
||||||
|
horiz
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let (Some(mut horiz_stack_rect), Some(mut vert_stack_rect), Some(corner_rect)) =
|
||||||
|
(horiz_stack_rect, vert_stack_rect, corner_rect)
|
||||||
|
{
|
||||||
|
geos.push(corner_rect);
|
||||||
|
|
||||||
|
let mut vert_geos = Vec::new();
|
||||||
|
let mut horiz_geos = Vec::new();
|
||||||
|
|
||||||
|
let vert_stack_count = ((win_count - 1) as f32 / 2.0).ceil() as i32;
|
||||||
|
let horiz_stack_count = ((win_count - 1) as f32 / 2.0).floor() as i32;
|
||||||
|
|
||||||
|
let vert_stack_y = vert_stack_rect.y;
|
||||||
|
let vert_win_height = vert_stack_rect.height as f32 / vert_stack_count as f32;
|
||||||
|
|
||||||
|
for i in 1..vert_stack_count {
|
||||||
|
let slice_point = vert_stack_y
|
||||||
|
+ (vert_win_height * i as f32).round() as i32
|
||||||
|
- gaps as i32 / 2;
|
||||||
|
|
||||||
|
let (to_push, rest) =
|
||||||
|
vert_stack_rect.split_at(Axis::Horizontal, slice_point, gaps);
|
||||||
|
|
||||||
|
vert_geos.push(to_push);
|
||||||
|
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
vert_stack_rect = rest;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vert_geos.push(vert_stack_rect);
|
||||||
|
|
||||||
|
let horiz_stack_x = horiz_stack_rect.x;
|
||||||
|
let horiz_win_width = horiz_stack_rect.width as f32 / horiz_stack_count as f32;
|
||||||
|
|
||||||
|
for i in 1..horiz_stack_count {
|
||||||
|
let slice_point = horiz_stack_x
|
||||||
|
+ (horiz_win_width * i as f32).round() as i32
|
||||||
|
- gaps as i32 / 2;
|
||||||
|
|
||||||
|
let (to_push, rest) =
|
||||||
|
horiz_stack_rect.split_at(Axis::Vertical, slice_point, gaps);
|
||||||
|
|
||||||
|
horiz_geos.push(to_push);
|
||||||
|
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
horiz_stack_rect = rest;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
horiz_geos.push(horiz_stack_rect);
|
||||||
|
|
||||||
|
for i in 0..(vert_geos.len() + horiz_geos.len()) {
|
||||||
|
if i % 2 == 0 {
|
||||||
|
geos.push(vert_geos[i / 2]);
|
||||||
|
} else {
|
||||||
|
geos.push(horiz_geos[i / 2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(inner_gaps) = inner_gaps {
|
||||||
|
for geo in geos.iter_mut() {
|
||||||
|
geo.x += inner_gaps as i32;
|
||||||
|
geo.y += inner_gaps as i32;
|
||||||
|
geo.width -= inner_gaps * 2;
|
||||||
|
geo.height -= inner_gaps * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct FairLayout {
|
||||||
|
pub gaps: Gaps,
|
||||||
|
pub axis: Axis,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FairLayout {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
gaps: Gaps::Absolute(8),
|
||||||
|
axis: Axis::Vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutGenerator for FairLayout {
|
||||||
|
fn layout(&self, args: &LayoutArgs) -> Vec<Geometry> {
|
||||||
|
let win_count = args.windows.len() as u32;
|
||||||
|
|
||||||
|
if win_count == 0 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = args.output_width;
|
||||||
|
let height = args.output_height;
|
||||||
|
|
||||||
|
let mut geos = Vec::<Geometry>::new();
|
||||||
|
|
||||||
|
let (outer_gaps, inner_gaps) = match self.gaps {
|
||||||
|
Gaps::Absolute(gaps) => (gaps, None),
|
||||||
|
Gaps::Split { inner, outer } => (outer, Some(inner)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let gaps = match inner_gaps {
|
||||||
|
Some(_) => 0,
|
||||||
|
None => outer_gaps,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rect = Geometry {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
.split_at(Axis::Horizontal, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Horizontal, (height - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, 0, outer_gaps)
|
||||||
|
.0
|
||||||
|
.split_at(Axis::Vertical, (width - outer_gaps) as i32, outer_gaps)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
if win_count == 1 {
|
||||||
|
geos.push(rect);
|
||||||
|
} else if win_count == 2 {
|
||||||
|
let len = match self.axis {
|
||||||
|
Axis::Vertical => rect.width,
|
||||||
|
Axis::Horizontal => rect.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
let coord = match self.axis {
|
||||||
|
Axis::Vertical => rect.x,
|
||||||
|
Axis::Horizontal => rect.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (rect1, rect2) =
|
||||||
|
rect.split_at(self.axis, coord + len as i32 / 2 - gaps as i32 / 2, gaps);
|
||||||
|
|
||||||
|
geos.push(rect1);
|
||||||
|
if let Some(rect2) = rect2 {
|
||||||
|
geos.push(rect2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let line_count = (win_count as f32).sqrt().round() as u32;
|
||||||
|
|
||||||
|
let mut wins_per_line = Vec::new();
|
||||||
|
|
||||||
|
let max_per_line = if win_count > line_count * line_count {
|
||||||
|
line_count + 1
|
||||||
|
} else {
|
||||||
|
line_count
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 1..=win_count {
|
||||||
|
let index = (i as f32 / max_per_line as f32).ceil() as usize - 1;
|
||||||
|
if wins_per_line.get(index).is_none() {
|
||||||
|
wins_per_line.push(0);
|
||||||
|
}
|
||||||
|
wins_per_line[index] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(wins_per_line.len(), line_count as usize);
|
||||||
|
|
||||||
|
let mut line_rects = Vec::new();
|
||||||
|
|
||||||
|
let (coord, len, axis) = match self.axis {
|
||||||
|
Axis::Horizontal => (
|
||||||
|
rect.y,
|
||||||
|
rect.height as f32 / line_count as f32,
|
||||||
|
Axis::Horizontal,
|
||||||
|
),
|
||||||
|
Axis::Vertical => (
|
||||||
|
rect.x,
|
||||||
|
rect.width as f32 / line_count as f32,
|
||||||
|
Axis::Vertical,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 1..line_count {
|
||||||
|
let slice_point = coord + (len * i as f32) as i32 - gaps as i32 / 2;
|
||||||
|
let (to_push, rest) = rect.split_at(axis, slice_point, gaps);
|
||||||
|
line_rects.push(to_push);
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
rect = rest;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line_rects.push(rect);
|
||||||
|
|
||||||
|
for (i, mut line_rect) in line_rects.into_iter().enumerate() {
|
||||||
|
let (coord, len, axis) = match self.axis {
|
||||||
|
Axis::Vertical => (
|
||||||
|
line_rect.y,
|
||||||
|
line_rect.height as f32 / wins_per_line[i] as f32,
|
||||||
|
Axis::Horizontal,
|
||||||
|
),
|
||||||
|
Axis::Horizontal => (
|
||||||
|
line_rect.x,
|
||||||
|
line_rect.width as f32 / wins_per_line[i] as f32,
|
||||||
|
Axis::Vertical,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
for j in 1..wins_per_line[i] {
|
||||||
|
let slice_point = coord + (len * j as f32) as i32 - gaps as i32 / 2;
|
||||||
|
let (to_push, rest) = line_rect.split_at(axis, slice_point, gaps);
|
||||||
|
geos.push(to_push);
|
||||||
|
if let Some(rest) = rest {
|
||||||
|
line_rect = rest;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos.push(line_rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(inner_gaps) = inner_gaps {
|
||||||
|
for geo in geos.iter_mut() {
|
||||||
|
geo.x += inner_gaps as i32;
|
||||||
|
geo.y += inner_gaps as i32;
|
||||||
|
geo.width -= inner_gaps * 2;
|
||||||
|
geo.height -= inner_gaps * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geos
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
#![deny(elided_lifetimes_in_paths)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
//! The Rust implementation of [Pinnacle](https://github.com/pinnacle-comp/pinnacle)'s
|
//! The Rust implementation of [Pinnacle](https://github.com/pinnacle-comp/pinnacle)'s
|
||||||
|
@ -87,6 +88,7 @@ use futures::{
|
||||||
Future, StreamExt,
|
Future, StreamExt,
|
||||||
};
|
};
|
||||||
use input::Input;
|
use input::Input;
|
||||||
|
use layout::Layout;
|
||||||
use output::Output;
|
use output::Output;
|
||||||
use pinnacle::Pinnacle;
|
use pinnacle::Pinnacle;
|
||||||
use process::Process;
|
use process::Process;
|
||||||
|
@ -102,6 +104,7 @@ use tower::service_fn;
|
||||||
use window::Window;
|
use window::Window;
|
||||||
|
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod layout;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
pub mod pinnacle;
|
pub mod pinnacle;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
@ -121,6 +124,7 @@ static INPUT: OnceLock<Input> = OnceLock::new();
|
||||||
static OUTPUT: OnceLock<Output> = OnceLock::new();
|
static OUTPUT: OnceLock<Output> = OnceLock::new();
|
||||||
static TAG: OnceLock<Tag> = OnceLock::new();
|
static TAG: OnceLock<Tag> = OnceLock::new();
|
||||||
static SIGNAL: OnceLock<RwLock<SignalState>> = OnceLock::new();
|
static SIGNAL: OnceLock<RwLock<SignalState>> = OnceLock::new();
|
||||||
|
static LAYOUT: OnceLock<Layout> = OnceLock::new();
|
||||||
|
|
||||||
/// A struct containing static references to all of the configuration structs.
|
/// A struct containing static references to all of the configuration structs.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -137,6 +141,8 @@ pub struct ApiModules {
|
||||||
pub output: &'static Output,
|
pub output: &'static Output,
|
||||||
/// The [`Tag`] struct
|
/// The [`Tag`] struct
|
||||||
pub tag: &'static Tag,
|
pub tag: &'static Tag,
|
||||||
|
/// The [`Layout`] struct
|
||||||
|
pub layout: &'static Layout,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connects to Pinnacle and builds the configuration structs.
|
/// Connects to Pinnacle and builds the configuration structs.
|
||||||
|
@ -154,7 +160,7 @@ pub async fn connect(
|
||||||
}))
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let (fut_sender, fut_recv) = unbounded_channel::<BoxFuture<()>>();
|
let (fut_sender, fut_recv) = unbounded_channel::<BoxFuture<'static, ()>>();
|
||||||
|
|
||||||
let pinnacle = PINNACLE.get_or_init(|| Pinnacle::new(channel.clone()));
|
let pinnacle = PINNACLE.get_or_init(|| Pinnacle::new(channel.clone()));
|
||||||
let process = PROCESS.get_or_init(|| Process::new(channel.clone(), fut_sender.clone()));
|
let process = PROCESS.get_or_init(|| Process::new(channel.clone(), fut_sender.clone()));
|
||||||
|
@ -162,6 +168,7 @@ pub async fn connect(
|
||||||
let input = INPUT.get_or_init(|| Input::new(channel.clone(), fut_sender.clone()));
|
let input = INPUT.get_or_init(|| Input::new(channel.clone(), fut_sender.clone()));
|
||||||
let tag = TAG.get_or_init(|| Tag::new(channel.clone()));
|
let tag = TAG.get_or_init(|| Tag::new(channel.clone()));
|
||||||
let output = OUTPUT.get_or_init(|| Output::new(channel.clone()));
|
let output = OUTPUT.get_or_init(|| Output::new(channel.clone()));
|
||||||
|
let layout = LAYOUT.get_or_init(|| Layout::new(channel.clone()));
|
||||||
|
|
||||||
SIGNAL
|
SIGNAL
|
||||||
.set(RwLock::new(SignalState::new(
|
.set(RwLock::new(SignalState::new(
|
||||||
|
@ -177,6 +184,7 @@ pub async fn connect(
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
tag,
|
tag,
|
||||||
|
layout,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((modules, fut_recv))
|
Ok((modules, fut_recv))
|
||||||
|
@ -190,7 +198,7 @@ pub async fn connect(
|
||||||
/// This function is inserted at the end of your config through the [`config`] macro.
|
/// This function is inserted at the end of your config through the [`config`] macro.
|
||||||
/// You should use the macro instead of this function directly.
|
/// You should use the macro instead of this function directly.
|
||||||
pub async fn listen(fut_recv: UnboundedReceiver<BoxFuture<'static, ()>>) {
|
pub async fn listen(fut_recv: UnboundedReceiver<BoxFuture<'static, ()>>) {
|
||||||
let mut future_set = FuturesUnordered::<BoxFuture<()>>::new();
|
let mut future_set = FuturesUnordered::<BoxFuture<'static, ()>>::new();
|
||||||
|
|
||||||
let mut fut_recv = UnboundedReceiverStream::new(fut_recv);
|
let mut fut_recv = UnboundedReceiverStream::new(fut_recv);
|
||||||
|
|
||||||
|
|
|
@ -313,7 +313,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_dc = dc_ping_recv_fuse => {
|
_dc = dc_ping_recv_fuse => {
|
||||||
println!("dc");
|
|
||||||
control_sender.send(Req::from_control(StreamControl::Disconnect)).expect("send failed");
|
control_sender.send(Req::from_control(StreamControl::Disconnect)).expect("send failed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,97 @@ pub struct Geometry {
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
||||||
|
pub enum Axis {
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Geometry {
|
||||||
|
pub fn split_at(mut self, axis: Axis, at: i32, thickness: u32) -> (Geometry, Option<Geometry>) {
|
||||||
|
match axis {
|
||||||
|
Axis::Horizontal => {
|
||||||
|
if at <= self.y {
|
||||||
|
let diff = at - self.y + thickness as i32;
|
||||||
|
if diff > 0 {
|
||||||
|
self.y += diff;
|
||||||
|
self.height = self.height.saturating_sub(diff as u32);
|
||||||
|
}
|
||||||
|
(self, None)
|
||||||
|
} else if at >= self.y + self.height as i32 {
|
||||||
|
(self, None)
|
||||||
|
} else if at + thickness as i32 >= self.y + self.height as i32 {
|
||||||
|
let diff = self.y + self.height as i32 - at;
|
||||||
|
self.height = self.height.saturating_sub(diff as u32);
|
||||||
|
(self, None)
|
||||||
|
} else {
|
||||||
|
let x = self.x;
|
||||||
|
let top_y = self.y;
|
||||||
|
let width = self.width;
|
||||||
|
let top_height = at - self.y;
|
||||||
|
|
||||||
|
let bot_y = at + thickness as i32;
|
||||||
|
let bot_height = self.y + self.height as i32 - at - thickness as i32;
|
||||||
|
|
||||||
|
let geo1 = Geometry {
|
||||||
|
x,
|
||||||
|
y: top_y,
|
||||||
|
width,
|
||||||
|
height: top_height as u32,
|
||||||
|
};
|
||||||
|
let geo2 = Geometry {
|
||||||
|
x,
|
||||||
|
y: bot_y,
|
||||||
|
width,
|
||||||
|
height: bot_height as u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
(geo1, Some(geo2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Axis::Vertical => {
|
||||||
|
if at <= self.x {
|
||||||
|
let diff = at - self.x + thickness as i32;
|
||||||
|
if diff > 0 {
|
||||||
|
self.x += diff;
|
||||||
|
self.width = self.width.saturating_sub(diff as u32);
|
||||||
|
}
|
||||||
|
(self, None)
|
||||||
|
} else if at >= self.x + self.width as i32 {
|
||||||
|
(self, None)
|
||||||
|
} else if at + thickness as i32 >= self.x + self.width as i32 {
|
||||||
|
let diff = self.x + self.width as i32 - at;
|
||||||
|
self.width = self.width.saturating_sub(diff as u32);
|
||||||
|
(self, None)
|
||||||
|
} else {
|
||||||
|
let left_x = self.x;
|
||||||
|
let y = self.y;
|
||||||
|
let left_width = at - self.x;
|
||||||
|
let height = self.height;
|
||||||
|
|
||||||
|
let right_x = at + thickness as i32;
|
||||||
|
let right_width = self.x + self.width as i32 - at - thickness as i32;
|
||||||
|
|
||||||
|
let geo1 = Geometry {
|
||||||
|
x: left_x,
|
||||||
|
y,
|
||||||
|
width: left_width as u32,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
let geo2 = Geometry {
|
||||||
|
x: right_x,
|
||||||
|
y,
|
||||||
|
width: right_width as u32,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
|
||||||
|
(geo1, Some(geo2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Batch a set of requests that will be sent ot the compositor all at once.
|
/// Batch a set of requests that will be sent ot the compositor all at once.
|
||||||
///
|
///
|
||||||
/// # Rationale
|
/// # Rationale
|
||||||
|
|
Loading…
Reference in a new issue