Add Rust layouts

This commit is contained in:
Ottatop 2024-03-15 20:51:12 -05:00
parent ab2b3ee13b
commit a98777c11e
6 changed files with 1154 additions and 35 deletions

View file

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

View file

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

View file

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

View file

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

View file

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