diff --git a/Cargo.lock b/Cargo.lock index cc16e5995..196007885 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1691,6 +1691,12 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "grid" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a" + [[package]] name = "guillotiere" version = "0.6.2" @@ -3204,6 +3210,7 @@ dependencies = [ "snowcap", "snowcap-api", "sysinfo 0.33.0", + "taffy", "temp-env", "tempfile", "test-log", @@ -3216,6 +3223,7 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", + "treediff", "vergen-gitcl", "x11rb", "xcursor", @@ -4203,6 +4211,18 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "taffy" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ee7dc8eeb09b5952509850485cf7e73b81af1a4cd5d71f2ee34ed2aa0c562a" +dependencies = [ + "arrayvec", + "grid", + "serde", + "slotmap", +] + [[package]] name = "temp-env" version = "0.3.6" @@ -4666,6 +4686,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "treediff" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ce481b2b7c2534fe7b5242cccebf37f9084392665c6a3783c414a1bada5432" + [[package]] name = "try-lock" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 54cb9b715..7c0c0be39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,6 +141,8 @@ indexmap = { workspace = true } snowcap = { path = "./snowcap", optional = true } snowcap-api = { path = "./snowcap/api/rust", optional = true } assert_matches = "1.5.0" +taffy = "0.7.2" +treediff = "5.0.0" [build-dependencies] vergen-gitcl = { version = "1.0.2", features = ["rustc", "cargo", "si"] } diff --git a/api/protobuf/pinnacle/layout/v1/layout.proto b/api/protobuf/pinnacle/layout/v1/layout.proto new file mode 100644 index 000000000..95dde6d98 --- /dev/null +++ b/api/protobuf/pinnacle/layout/v1/layout.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package pinnacle.layout.v1; + +message LayoutTree { + uint32 tree_id = 1; + float outer_gaps = 2; + float inner_gaps = 3; + LayoutNode root = 4; +} + +message LayoutNode { + uint32 node_id = 1; + NodeStyle style = 2; + repeated LayoutNode children = 3; +} + +enum FlexDir { + FLEX_DIR_UNSPECIFIED = 0; + FLEX_DIR_ROW = 1; + FLEX_DIR_COLUMN = 2; +} + +message NodeStyle { + FlexDir flex_dir = 1; + float size_proportion = 2; +} + +message LayoutRequest { + message TreeResponse { + uint32 request_id = 1; + LayoutTree tree = 2; + string output_name = 3; + } + + message ForceLayout { + string output_name = 1; + } + + oneof request { + TreeResponse tree_response = 1; + ForceLayout force_layout = 2; + } +} + +message LayoutResponse { + uint32 request_id = 1; + string output_name = 2; + uint32 window_count = 3; + repeated uint32 tag_ids = 4; +} + +service LayoutService { + rpc Layout(stream LayoutRequest) returns (stream LayoutResponse); +} diff --git a/api/rust/examples/default_config/main.rs b/api/rust/examples/default_config/main.rs index b67bd5b4a..a14ad918d 100644 --- a/api/rust/examples/default_config/main.rs +++ b/api/rust/examples/default_config/main.rs @@ -4,10 +4,15 @@ use pinnacle_api::input::libinput::Capability; use pinnacle_api::input::Bind; use pinnacle_api::input::BindLayer; use pinnacle_api::input::Keysym; -use pinnacle_api::layout::{ - CornerLayout, CornerLocation, CyclingLayoutManager, DwindleLayout, FairLayout, MasterSide, - MasterStackLayout, SpiralLayout, -}; +use pinnacle_api::layout; +use pinnacle_api::layout::CornerLayout; +use pinnacle_api::layout::CornerLocation; +use pinnacle_api::layout::CyclingLayoutManager; +use pinnacle_api::layout::DwindleLayout; +use pinnacle_api::layout::FairLayout; +use pinnacle_api::layout::MasterSide; +use pinnacle_api::layout::MasterStackLayout; +use pinnacle_api::layout::SpiralLayout; use pinnacle_api::output; use pinnacle_api::output::OutputSetup; use pinnacle_api::pinnacle; @@ -34,7 +39,6 @@ async fn main() { // Deconstruct to get all the APIs. #[allow(unused_variables)] let ApiModules { - layout, render, #[cfg(feature = "snowcap")] snowcap, @@ -230,7 +234,8 @@ async fn main() { // Create a `CyclingLayoutManager` that can cycle between layouts on different tags. // // It takes in some layout generators that need to be boxed and dyn-coerced. - let layout_requester = layout.set_manager(CyclingLayoutManager::new([ + + let layout_requester = layout::set_manager(CyclingLayoutManager::new([ Box::::default() as _, Box::new(MasterStackLayout { master_side: MasterSide::Right, @@ -295,7 +300,7 @@ async fn main() { }; let Some(first_active_tag) = focused_op .tags() - .batch_find(|tg| Box::pin(tg.active_async()), |active| *active) + .batch_find(|tag| Box::pin(tag.active_async()), |active| *active) else { return; }; @@ -362,11 +367,6 @@ async fn main() { } input::libinput::for_all_devices(|device| { - // TODO: remove this - if device.capabilities().contains(Capability::POINTER) { - device.set_accel_profile(AccelProfile::Flat); - } - if device.get_type().is_touchpad() { device.set_natural_scroll(true); } diff --git a/api/rust/src/client.rs b/api/rust/src/client.rs index 3de3a69f1..421fc1f27 100644 --- a/api/rust/src/client.rs +++ b/api/rust/src/client.rs @@ -1,5 +1,6 @@ use pinnacle_api_defs::pinnacle::{ input::v1::input_service_client::InputServiceClient, + layout::v1::layout_service_client::LayoutServiceClient, output::v1::output_service_client::OutputServiceClient, process::v1::process_service_client::ProcessServiceClient, tag::v1::tag_service_client::TagServiceClient, @@ -20,6 +21,7 @@ pub struct Client { output: OutputServiceClient, input: InputServiceClient, process: ProcessServiceClient, + layout: LayoutServiceClient, } impl Client { @@ -59,6 +61,10 @@ impl Client { Self::get().process.clone() } + pub fn layout() -> LayoutServiceClient { + Self::get().layout.clone() + } + fn new(channel: Channel) -> Self { Self { pinnacle: PinnacleServiceClient::new(channel.clone()), @@ -67,6 +73,7 @@ impl Client { output: OutputServiceClient::new(channel.clone()), input: InputServiceClient::new(channel.clone()), process: ProcessServiceClient::new(channel.clone()), + layout: LayoutServiceClient::new(channel.clone()), } } } diff --git a/api/rust/src/layout.rs b/api/rust/src/layout.rs index e7c7c7238..65cfaf024 100644 --- a/api/rust/src/layout.rs +++ b/api/rust/src/layout.rs @@ -7,100 +7,212 @@ //! TODO: finish this documentation use std::{ + cell::RefCell, collections::HashMap, - sync::{Arc, Mutex}, + rc::Rc, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, + }, }; -use pinnacle_api_defs::pinnacle::layout::v0alpha1::{ - layout_request::{Body, ExplicitLayout, Geometries}, - LayoutRequest, -}; +use pinnacle_api_defs::pinnacle::layout::v1::{layout_request, LayoutRequest}; use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; use tokio_stream::StreamExt; use tracing::debug; -use crate::{ - block_on_tokio, layout, - output::OutputHandle, - tag::TagHandle, - util::{Axis, Geometry}, - window::WindowHandle, -}; - -/// A struct that allows you to manage layouts. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] -pub struct Layout; +use crate::{client::Client, output::OutputHandle, tag::TagHandle, util::Axis, BlockOnTokio}; -impl Layout { - /// Consume the given [`LayoutManager`] and set it as the global layout handler. - /// - /// This returns a [`LayoutRequester`] that allows you to manually request layouts from - /// the compositor. The requester also contains your layout manager wrapped in an `Arc` - /// to allow you to mutate its settings. - pub fn set_manager(&self, manager: M) -> LayoutRequester - where - M: LayoutManager + Send + 'static, - { - let (from_client, to_server) = unbounded_channel::(); - let to_server_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(to_server); - let mut from_server = block_on_tokio(layout().clone().layout(to_server_stream)) - .expect("TODO") - .into_inner(); - - let from_client_clone = from_client.clone(); - - let manager = Arc::new(Mutex::new(manager)); - - let requester = LayoutRequester { - sender: from_client_clone, - manager: manager.clone(), - }; - - let fut = async move { - while let Some(Ok(response)) = from_server.next().await { - let args = LayoutArgs { - output: OutputHandle { - name: response.output_name().to_string(), - }, - windows: response - .window_ids - .into_iter() - .map(|id| WindowHandle { id }) - .collect(), - tags: response - .tag_ids - .into_iter() - .map(|id| TagHandle { id }) - .collect(), - output_width: response.output_width.unwrap_or_default(), - output_height: response.output_height.unwrap_or_default(), - }; - let geos = manager.lock().unwrap().active_layout(&args).layout(&args); - if from_client - .send(LayoutRequest { - body: Some(Body::Geometries(Geometries { +/// Consume the given [`LayoutManager`] and set it as the global layout handler. +/// +/// This returns a [`LayoutRequester`] that allows you to manually request layouts from +/// the compositor. The requester also contains your layout manager wrapped in an `Arc` +/// to allow you to mutate its settings. +pub fn set_manager(manager: M) -> LayoutRequester +where + M: LayoutManager + Send + 'static, +{ + let (from_client, to_server) = unbounded_channel::(); + let to_server_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(to_server); + let mut from_server = Client::layout() + .layout(to_server_stream) + .block_on_tokio() + .unwrap() + .into_inner(); + + let from_client_clone = from_client.clone(); + + let manager = Arc::new(Mutex::new(manager)); + + let requester = LayoutRequester { + sender: from_client_clone, + manager: manager.clone(), + }; + + let fut = async move { + while let Some(Ok(response)) = from_server.next().await { + let args = LayoutArgs { + output: OutputHandle { + name: response.output_name.clone(), + }, + window_count: response.window_count, + tags: response + .tag_ids + .into_iter() + .map(|id| TagHandle { id }) + .collect(), + }; + let tree = manager.lock().unwrap().active_layout(&args).layout(&args); + if from_client + .send(LayoutRequest { + request: Some(layout_request::Request::TreeResponse( + layout_request::TreeResponse { request_id: response.request_id, + tree: Some(pinnacle_api_defs::pinnacle::layout::v1::LayoutTree { + tree_id: tree.tree_id, + root: tree.root.map(|root| root.into()), + inner_gaps: tree.inner_gaps, + outer_gaps: tree.outer_gaps, + }), 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(), - })), - }) - .is_err() - { - debug!("Failed to send layout geometries: channel closed"); - } + }, + )), + }) + .is_err() + { + debug!("Failed to send layout geometries: channel closed"); } - }; + } + }; + + tokio::spawn(fut); + requester +} + +#[derive(Debug, Clone)] +pub struct LayoutNode { + id_ctr: Arc, + inner: Rc>, +} - tokio::spawn(fut); - requester +#[derive(Debug)] +struct LayoutNodeInner { + node_id: u32, + style: Style, + children: Vec, +} + +impl LayoutNodeInner { + fn new(id: u32) -> Self { + LayoutNodeInner { + node_id: id, + style: Style { + layout_dir: LayoutDir::Row, + size_proportion: 1.0, + }, + children: Vec::new(), + } + } +} + +impl LayoutNode { + pub fn add_child(&self, child: Self) { + self.inner.borrow_mut().children.push(child); + } + + pub fn set_children(&self, children: impl IntoIterator) { + self.inner.borrow_mut().children.extend(children); + } + + pub fn dir(&self, dir: LayoutDir) -> &Self { + self.inner.borrow_mut().style.layout_dir = dir; + self + } + + pub fn size_proportion(&self, proportion: f32) -> &Self { + self.inner.borrow_mut().style.size_proportion = proportion; + self + } +} + +#[derive(Default, Debug)] +pub struct LayoutTree { + tree_id: u32, + inner_gaps: f32, + outer_gaps: f32, + id_ctr: Arc, + root: Option, +} + +impl LayoutTree { + pub fn new(tree_id: u32) -> Self { + Self { + tree_id, + ..Default::default() + } + } + + pub fn with_gaps(mut self, gaps: Gaps) -> Self { + let (inner, outer) = gaps.to_inner_outer(); + self.inner_gaps = inner; + self.outer_gaps = outer; + self + } + + pub fn new_node(&self) -> LayoutNode { + LayoutNode { + id_ctr: self.id_ctr.clone(), + inner: Rc::new(RefCell::new(LayoutNodeInner::new( + self.id_ctr.fetch_add(1, Ordering::Relaxed), + ))), + } + } + + /// Creates and returns the root layout node. + pub fn set_root(&mut self, node: LayoutNode) { + self.root.replace(node); + } +} + +#[derive(Debug)] +pub enum LayoutDir { + Row, + Column, +} + +#[derive(Debug)] +pub struct Style { + layout_dir: LayoutDir, + size_proportion: f32, +} + +impl From for pinnacle_api_defs::pinnacle::layout::v1::LayoutNode { + fn from(value: LayoutNode) -> Self { + fn api_node_from_layout_node( + node: LayoutNode, + ) -> pinnacle_api_defs::pinnacle::layout::v1::LayoutNode { + pinnacle_api_defs::pinnacle::layout::v1::LayoutNode { + node_id: node.inner.borrow().node_id, + style: Some(pinnacle_api_defs::pinnacle::layout::v1::NodeStyle { + flex_dir: match node.inner.borrow().style.layout_dir { + LayoutDir::Row => pinnacle_api_defs::pinnacle::layout::v1::FlexDir::Row, + LayoutDir::Column => { + pinnacle_api_defs::pinnacle::layout::v1::FlexDir::Column + } + } + .into(), + size_proportion: node.inner.borrow().style.size_proportion, + }), + children: node + .inner + .borrow() + .children + .iter() + .map(|node| api_node_from_layout_node(node.clone())) + .collect(), + } + } + api_node_from_layout_node(value) } } @@ -109,14 +221,10 @@ impl Layout { pub struct LayoutArgs { /// The output that is being laid out. pub output: OutputHandle, - /// The windows that are being laid out. - pub windows: Vec, + /// The number of windows being laid out. + pub window_count: u32, /// The *focused* tags on the output. pub tags: Vec, - /// The width of the layout area, in pixels. - pub output_width: u32, - /// The height of the layout area, in pixels. - pub output_height: u32, } /// Types that can manage layouts. @@ -128,7 +236,7 @@ pub trait LayoutManager { /// Types that can generate layouts by computing a vector of [geometries][Geometry]. pub trait LayoutGenerator { /// Generate a vector of [geometries][Geometry] using the given [`LayoutArgs`]. - fn layout(&self, args: &LayoutArgs) -> Vec; + fn layout(&self, args: &LayoutArgs) -> LayoutTree; } /// Gaps between windows. @@ -151,6 +259,15 @@ pub enum Gaps { }, } +impl Gaps { + fn to_inner_outer(self) -> (f32, f32) { + match self { + Gaps::Absolute(abs) => (abs as f32 / 2.0, abs as f32 / 2.0), + Gaps::Split { inner, outer } => (inner as f32, outer as f32), + } + } +} + /// A [`LayoutManager`] that keeps track of layouts per output and provides /// methods to cycle between them. pub struct CyclingLayoutManager { @@ -240,11 +357,15 @@ impl LayoutRequester { /// This uses the focused output for the request. /// If you want to layout a specific output, see [`LayoutRequester::request_layout_on_output`]. pub fn request_layout(&self) { - let output_name = crate::output::get_focused().map(|op| op.name); + let Some(output_name) = crate::output::get_focused().map(|op| op.name) else { + return; + }; if self .sender .send(LayoutRequest { - body: Some(Body::Layout(ExplicitLayout { output_name })), + request: Some(layout_request::Request::ForceLayout( + layout_request::ForceLayout { output_name }, + )), }) .is_err() { @@ -257,9 +378,11 @@ impl LayoutRequester { if self .sender .send(LayoutRequest { - body: Some(Body::Layout(ExplicitLayout { - output_name: Some(output.name.clone()), - })), + request: Some(layout_request::Request::ForceLayout( + layout_request::ForceLayout { + output_name: output.name.clone(), + }, + )), }) .is_err() { @@ -286,8 +409,8 @@ impl LayoutRequester { struct NoopLayout; impl LayoutGenerator for NoopLayout { - fn layout(&self, _args: &LayoutArgs) -> Vec { - Vec::new() + fn layout(&self, _args: &LayoutArgs) -> LayoutTree { + LayoutTree::default() } } @@ -340,162 +463,68 @@ impl Default for MasterStackLayout { } impl LayoutGenerator for MasterStackLayout { - fn layout(&self, args: &LayoutArgs) -> Vec { - let win_count = args.windows.len() as u32; + fn layout(&self, args: &LayoutArgs) -> LayoutTree { + let win_count = args.window_count; if win_count == 0 { - return Vec::new(); + return LayoutTree::default(); } - let width = args.output_width; - let height = args.output_height; - - let mut geos = Vec::::new(); + let mut tree = LayoutTree::new(0).with_gaps(self.gaps); + let root = tree.new_node(); + root.dir(match self.master_side { + MasterSide::Left | MasterSide::Right => LayoutDir::Row, + MasterSide::Top | MasterSide::Bottom => LayoutDir::Column, + }); - 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 - }; + tree.set_root(root.clone()); - let gaps = match inner_gaps { - Some(_) => 0, - None => outer_gaps, - }; + let master_factor = self.master_factor.clamp(0.1, 0.9); - 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 master_side_node = tree.new_node(); - let mut master_rect = master_rect.unwrap_or_else(|| stack_rect.take().unwrap()); + master_side_node + .dir(match self.master_side { + MasterSide::Left | MasterSide::Right => LayoutDir::Column, + MasterSide::Top | MasterSide::Bottom => LayoutDir::Row, + }) + .size_proportion(master_factor * 10.0); - let (master_count, stack_count) = if win_count > self.master_count { - (self.master_count, Some(win_count - self.master_count)) - } else { - (win_count, None) - }; + for _ in 0..u32::min(win_count, self.master_count) { + let child = tree.new_node(); + child.size_proportion(10.0); + master_side_node.add_child(child); + } - 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, - ), - }; + let stack_side_node = tree.new_node(); + stack_side_node + .dir(match self.master_side { + MasterSide::Left | MasterSide::Right => LayoutDir::Column, + MasterSide::Top | MasterSide::Bottom => LayoutDir::Row, + }) + .size_proportion((1.0 - master_factor) * 10.0); - 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; - } - } + for _ in self.master_count..win_count { + let child = tree.new_node(); + child.size_proportion(10.0); + stack_side_node.add_child(child); } - 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 win_count <= self.master_count { + root.set_children([master_side_node]); + return tree; } - 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; + match self.master_side { + MasterSide::Left | MasterSide::Top => { + root.set_children([master_side_node, stack_side_node]); + } + MasterSide::Right | MasterSide::Bottom => { + root.set_children([stack_side_node, master_side_node]); } } - geos + tree } } @@ -526,88 +555,46 @@ impl Default for DwindleLayout { } impl LayoutGenerator for DwindleLayout { - fn layout(&self, args: &LayoutArgs) -> Vec { - let win_count = args.windows.len() as u32; + fn layout(&self, args: &LayoutArgs) -> LayoutTree { + let win_count = args.window_count; if win_count == 0 { - return Vec::new(); + return LayoutTree::default(); } - let width = args.output_width; - let height = args.output_height; + let mut tree = LayoutTree::new(0).with_gaps(self.gaps); + let root = tree.new_node(); + root.dir(LayoutDir::Row); - let mut geos = Vec::::new(); + tree.set_root(root.clone()); - let (outer_gaps, inner_gaps) = match self.gaps { - Gaps::Absolute(gaps) => (gaps, None), - Gaps::Split { inner, outer } => (outer, Some(inner)), - }; + if win_count == 1 { + return tree; + } - let gaps = match inner_gaps { - Some(_) => 0, - None => outer_gaps, - }; + let windows_left = win_count - 1; - 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; + let mut current_node = root.clone(); - 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; - } - } + for i in 0..windows_left { + let child1 = tree.new_node(); + child1.dir(match i % 2 == 0 { + true => LayoutDir::Column, + false => LayoutDir::Row, + }); + current_node.add_child(child1); - geos.push(rect) - } + let child2 = tree.new_node(); + child2.dir(match i % 2 == 0 { + true => LayoutDir::Column, + false => LayoutDir::Row, + }); + current_node.add_child(child2.clone()); - 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; - } + current_node = child2; } - geos + tree } } @@ -640,96 +627,50 @@ impl Default for SpiralLayout { } impl LayoutGenerator for SpiralLayout { - fn layout(&self, args: &LayoutArgs) -> Vec { - let win_count = args.windows.len() as u32; + fn layout(&self, args: &LayoutArgs) -> LayoutTree { + let win_count = args.window_count; if win_count == 0 { - return Vec::new(); + return LayoutTree::default(); } - let width = args.output_width; - let height = args.output_height; - - let mut geos = Vec::::new(); - - let (outer_gaps, inner_gaps) = match self.gaps { - Gaps::Absolute(gaps) => (gaps, None), - Gaps::Split { inner, outer } => (outer, Some(inner)), - }; + let mut tree = LayoutTree::new(0).with_gaps(self.gaps); + let root = tree.new_node(); + root.dir(LayoutDir::Row); - 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; + tree.set_root(root.clone()); 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) + return tree; } - 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; - } + let windows_left = win_count - 1; + + let mut current_node = root; + + for i in 0..windows_left { + let child1 = tree.new_node(); + child1.dir(match i % 2 == 0 { + true => LayoutDir::Column, + false => LayoutDir::Row, + }); + current_node.add_child(child1.clone()); + + let child2 = tree.new_node(); + child2.dir(match i % 2 == 0 { + true => LayoutDir::Column, + false => LayoutDir::Row, + }); + current_node.add_child(child2.clone()); + + current_node = match i % 4 { + 0 | 1 => child2, + 2 | 3 => child1, + _ => unreachable!(), + }; } - geos + tree } } @@ -778,172 +719,77 @@ impl Default for CornerLayout { } impl LayoutGenerator for CornerLayout { - fn layout(&self, args: &LayoutArgs) -> Vec { - let win_count = args.windows.len() as u32; + fn layout(&self, args: &LayoutArgs) -> LayoutTree { + let win_count = args.window_count; if win_count == 0 { - return Vec::new(); + return LayoutTree::default(); } - let width = args.output_width; - let height = args.output_height; - - let mut geos = Vec::::new(); + let mut tree = LayoutTree::new(0).with_gaps(self.gaps); + let root = tree.new_node(); + root.dir(LayoutDir::Row); - 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; + tree.set_root(root.clone()); 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; - } - } + return tree; + } - vert_geos.push(vert_stack_rect); + let corner_width_factor = self.corner_width_factor.clamp(0.1, 0.9); + let corner_height_factor = self.corner_height_factor.clamp(0.1, 0.9); - let horiz_stack_x = horiz_stack_rect.x; - let horiz_win_width = horiz_stack_rect.width as f32 / horiz_stack_count as f32; + let corner_and_horiz_stack_node = tree.new_node(); + corner_and_horiz_stack_node + .dir(LayoutDir::Column) + .size_proportion(corner_width_factor * 10.0); - 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 vert_stack_node = tree.new_node(); + vert_stack_node + .dir(LayoutDir::Column) + .size_proportion((1.0 - corner_width_factor) * 10.0); - let (to_push, rest) = - horiz_stack_rect.split_at(Axis::Vertical, slice_point, gaps); + root.set_children(match self.corner_loc { + CornerLocation::TopLeft | CornerLocation::BottomLeft => { + [corner_and_horiz_stack_node.clone(), vert_stack_node.clone()] + } + CornerLocation::TopRight | CornerLocation::BottomRight => { + [vert_stack_node.clone(), corner_and_horiz_stack_node.clone()] + } + }); - horiz_geos.push(to_push); + if win_count == 2 { + return tree; + } - if let Some(rest) = rest { - horiz_stack_rect = rest; - } else { - break; - } - } + let corner_node = tree.new_node(); + corner_node.size_proportion(corner_height_factor * 10.0); - horiz_geos.push(horiz_stack_rect); + let horiz_stack_node = tree.new_node(); + horiz_stack_node + .dir(LayoutDir::Row) + .size_proportion((1.0 - corner_height_factor) * 10.0); - 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]); - } - } - } + corner_and_horiz_stack_node.set_children(match self.corner_loc { + CornerLocation::TopLeft | CornerLocation::TopRight => { + [corner_node, horiz_stack_node.clone()] } - } + CornerLocation::BottomLeft | CornerLocation::BottomRight => { + [horiz_stack_node.clone(), corner_node] + } + }); - 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; + for i in 0..win_count - 1 { + if i % 2 == 0 { + let child = tree.new_node(); + vert_stack_node.add_child(child); + } else { + let child = tree.new_node(); + horiz_stack_node.add_child(child); } } - geos + tree } } @@ -971,150 +817,215 @@ impl Default for FairLayout { } impl LayoutGenerator for FairLayout { - fn layout(&self, args: &LayoutArgs) -> Vec { - let win_count = args.windows.len() as u32; + fn layout(&self, args: &LayoutArgs) -> LayoutTree { + let win_count = args.window_count; if win_count == 0 { - return Vec::new(); + return LayoutTree::default(); } - let width = args.output_width; - let height = args.output_height; - - let mut geos = Vec::::new(); - - let (outer_gaps, inner_gaps) = match self.gaps { - Gaps::Absolute(gaps) => (gaps, None), - Gaps::Split { inner, outer } => (outer, Some(inner)), - }; + let mut tree = LayoutTree::new(0).with_gaps(self.gaps); + let root = tree.new_node(); + root.dir(match self.axis { + Axis::Horizontal => LayoutDir::Column, + Axis::Vertical => LayoutDir::Row, + }); - let gaps = match inner_gaps { - Some(_) => 0, - None => outer_gaps, - }; + tree.set_root(root.clone()); - let mut rect = Geometry { - x: 0, - y: 0, - width, - height, + if win_count == 1 { + return tree; } - .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, - }; + if win_count == 2 { + let child1 = tree.new_node(); + let child2 = tree.new_node(); + root.set_children([child1, child2]); + return tree; + } - let coord = match self.axis { - Axis::Vertical => rect.x, - Axis::Horizontal => rect.y, - }; + let line_count = (win_count as f32).sqrt().round() as u32; - let (rect1, rect2) = - rect.split_at(self.axis, coord + len as i32 / 2 - gaps as i32 / 2, gaps); + let mut wins_per_line = Vec::new(); - geos.push(rect1); - if let Some(rect2) = rect2 { - geos.push(rect2); - } + let max_per_line = if win_count > line_count * line_count { + line_count + 1 } 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 - }; + 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; + 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, - ), - }; + let lines = wins_per_line.into_iter().map(|win_ct| { + let line_root = tree.new_node(); + line_root.dir(match self.axis { + Axis::Horizontal => LayoutDir::Row, + Axis::Vertical => LayoutDir::Column, + }); - 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; - } + for _ in 0..win_ct { + let child = tree.new_node(); + line_root.add_child(child); } - 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); - } - } + line_root + }); - 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; - } - } + root.set_children(lines); - geos + tree } } +// fn layout(&self, args: &LayoutArgs) -> Vec { +// 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::::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 +// } +// } diff --git a/api/rust/src/lib.rs b/api/rust/src/lib.rs index b79bd590b..283bc618f 100644 --- a/api/rust/src/lib.rs +++ b/api/rust/src/lib.rs @@ -87,9 +87,7 @@ use client::Client; use futures::{Future, StreamExt}; use hyper_util::rt::TokioIo; -use layout::Layout; use pinnacle_api_defs::pinnacle::{ - layout::v0alpha1::layout_service_client::LayoutServiceClient, render::v0alpha1::render_service_client::RenderServiceClient, signal::v1::signal_service_client::SignalServiceClient, }; @@ -126,17 +124,11 @@ pub use tokio; // tonic doesn't like it when you use clients across tokio runtimes, and these are static // meaning they would get reused, so this allows us to recreate the client on a // different runtime when testing. -static LAYOUT: RwLock>> = RwLock::const_new(None); static RENDER: RwLock>> = RwLock::const_new(None); static SIGNAL: RwLock>> = RwLock::const_new(None); static SIGNAL_MODULE: Mutex> = Mutex::const_new(None); -pub(crate) fn layout() -> LayoutServiceClient { - block_on_tokio(LAYOUT.read()) - .clone() - .expect("grpc connection was not initialized") -} pub(crate) fn render() -> RenderServiceClient { block_on_tokio(RENDER.read()) .clone() @@ -162,8 +154,6 @@ pub(crate) fn signal_module() -> MappedMutexGuard<'static, SignalState> { #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ApiModules { - /// The [`Layout`] struct - pub layout: &'static Layout, /// The [`Render`] struct pub render: &'static Render, @@ -182,7 +172,6 @@ impl ApiModules { /// Creates all the API modules. pub const fn new() -> Self { Self { - layout: &Layout, render: &Render, #[cfg(feature = "snowcap")] snowcap: { @@ -218,10 +207,6 @@ pub async fn connect() -> Result<(), Box> { .write() .await .replace(RenderServiceClient::new(channel.clone())); - LAYOUT - .write() - .await - .replace(LayoutServiceClient::new(channel.clone())); SIGNAL .write() .await diff --git a/api/rust/src/tag.rs b/api/rust/src/tag.rs index 9b7d7c9d8..bcb3cc7d9 100644 --- a/api/rust/src/tag.rs +++ b/api/rust/src/tag.rs @@ -186,25 +186,11 @@ pub fn connect_signal(signal: TagSignal) -> SignalHandle { /// A handle to a tag. /// /// This handle allows you to do things like switch to tags and get their properties. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TagHandle { pub(crate) id: u32, } -impl PartialEq for TagHandle { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for TagHandle {} - -impl std::hash::Hash for TagHandle { - fn hash(&self, state: &mut H) { - self.id.hash(state); - } -} - impl TagHandle { /// Activate this tag and deactivate all other ones on the same output. /// diff --git a/pinnacle-api-defs/src/lib.rs b/pinnacle-api-defs/src/lib.rs index bb70bf2c4..743f24f67 100644 --- a/pinnacle-api-defs/src/lib.rs +++ b/pinnacle-api-defs/src/lib.rs @@ -102,6 +102,10 @@ pub mod pinnacle { pub mod v0alpha1 { tonic::include_proto!("pinnacle.layout.v0alpha1"); } + + pub mod v1 { + tonic::include_proto!("pinnacle.layout.v1"); + } } pub mod render { diff --git a/src/api/layout.rs b/src/api/layout.rs index f6b6ca565..ff97ef8fa 100644 --- a/src/api/layout.rs +++ b/src/api/layout.rs @@ -1,13 +1,6 @@ -use pinnacle_api_defs::pinnacle::layout::v0alpha1::{ - layout_request::{self, ExplicitLayout}, - layout_service_server, LayoutRequest, LayoutResponse, -}; -use tonic::{Request, Response, Status, Streaming}; -use tracing::debug; +use super::StateFnSender; -use crate::output::OutputName; - -use super::{run_bidirectional_streaming, ResponseStream, StateFnSender}; +mod v1; pub struct LayoutService { sender: StateFnSender, @@ -18,43 +11,3 @@ impl LayoutService { Self { sender } } } - -#[tonic::async_trait] -impl layout_service_server::LayoutService for LayoutService { - type LayoutStream = ResponseStream; - - async fn layout( - &self, - request: Request>, - ) -> Result, Status> { - let in_stream = request.into_inner(); - - run_bidirectional_streaming( - self.sender.clone(), - in_stream, - |state, request| { - if let Some(body) = request.body { - match body { - layout_request::Body::Geometries(geos) => { - if let Err(err) = state.apply_layout(geos) { - debug!("{err}") - } - } - layout_request::Body::Layout(ExplicitLayout { output_name }) => { - if let Some(output) = output_name - .map(OutputName) - .and_then(|name| name.output(&state.pinnacle)) - .or_else(|| state.pinnacle.focused_output().cloned()) - { - state.pinnacle.request_layout(&output); - } - } - } - } - }, - |state, sender, _join_handle| { - state.pinnacle.layout_state.layout_request_sender = Some(sender); - }, - ) - } -} diff --git a/src/api/layout/v1.rs b/src/api/layout/v1.rs new file mode 100644 index 000000000..04f7d0e1f --- /dev/null +++ b/src/api/layout/v1.rs @@ -0,0 +1,136 @@ +use pinnacle_api_defs::pinnacle::layout::{ + self, + v1::{LayoutRequest, LayoutResponse}, +}; +use tokio::sync::mpsc::unbounded_channel; +use tonic::{Request, Streaming}; + +use crate::{ + api::{run_bidirectional_streaming, ResponseStream, TonicResult}, + layout::LayoutInfo, + output::OutputName, +}; + +#[tonic::async_trait] +impl layout::v1::layout_service_server::LayoutService for super::LayoutService { + type LayoutStream = ResponseStream; + + async fn layout( + &self, + request: Request>, + ) -> TonicResult { + let in_stream = request.into_inner(); + + run_bidirectional_streaming( + self.sender.clone(), + in_stream, + |state, request| { + let Some(request) = request.request else { + return; + }; + + match request { + layout::v1::layout_request::Request::TreeResponse(tree_response) => { + let tree = tree_response.tree.unwrap(); + let tree_id = tree.tree_id; + + let tree = match crate::layout::tree::LayoutTree::try_from(tree) { + Ok(tree) => tree, + Err(()) => { + tracing::debug!("failed to create layout tree"); + return; + } + }; + + if let Err(err) = state.apply_layout_tree( + tree_id, + tree, + tree_response.request_id, + tree_response.output_name, + ) { + tracing::debug!("{err}") + } + } + layout::v1::layout_request::Request::ForceLayout(force_layout) => { + let output_name = force_layout.output_name; + if let Some(output) = OutputName(output_name) + .output(&state.pinnacle) + .or_else(|| state.pinnacle.focused_output().cloned()) + { + state.pinnacle.request_layout(&output); + } + } + } + }, + |state, sender, _join_handle| { + let (send, mut recv) = unbounded_channel::(); + tokio::spawn(async move { + while let Some(info) = recv.recv().await { + if sender + .send(Ok(LayoutResponse { + request_id: info.request_id.to_inner(), + output_name: info.output_name.0, + window_count: info.window_count, + tag_ids: info.tag_ids.into_iter().map(|id| id.to_inner()).collect(), + })) + .is_err() + { + break; + } + } + }); + state + .pinnacle + .layout_state + .layout_request_sender + .replace(send); + }, + ) + } +} + +impl TryFrom for crate::layout::tree::LayoutNode { + type Error = (); + + fn try_from(node: layout::v1::LayoutNode) -> Result { + let style = node.style.ok_or(())?; + + let taffy_style = taffy::Style { + flex_direction: match style.flex_dir() { + layout::v1::FlexDir::Unspecified | layout::v1::FlexDir::Row => { + taffy::FlexDirection::Row + } + layout::v1::FlexDir::Column => taffy::FlexDirection::Column, + }, + flex_basis: taffy::Dimension::Percent(style.size_proportion), + ..Default::default() + }; + + Ok(Self { + style: taffy_style, + children: node + .children + .into_iter() + .map(|child| { + let node_id = child.node_id; + Self::try_from(child).map(|child| (node_id, child)) + }) + .collect::, _>>()?, + }) + } +} + +impl TryFrom for crate::layout::tree::LayoutTree { + type Error = (); + + fn try_from(tree: layout::v1::LayoutTree) -> Result { + let root = tree.root.ok_or(())?; + let root_id = root.node_id; + Ok(Self::new( + root.try_into()?, + root_id, + tree.inner_gaps, + tree.outer_gaps, + )) + } +} diff --git a/src/config.rs b/src/config.rs index f2e639cef..9bc5bb683 100644 --- a/src/config.rs +++ b/src/config.rs @@ -479,7 +479,7 @@ impl Pinnacle { .add_service(pinnacle_api_defs::pinnacle::input::v1::input_service_server::InputServiceServer::new(input_service)) .add_service(pinnacle_api_defs::pinnacle::process::v1::process_service_server::ProcessServiceServer::new(process_service)) .add_service(pinnacle_api_defs::pinnacle::signal::v1::signal_service_server::SignalServiceServer::new(signal_service)) - .add_service(LayoutServiceServer::new(layout_service)) + .add_service(pinnacle_api_defs::pinnacle::layout::v1::layout_service_server::LayoutServiceServer::new(layout_service)) .add_service(RenderServiceServer::new(render_service)); self.grpc_server_join_handle = Some(tokio::spawn(async move { diff --git a/src/layout.rs b/src/layout.rs index ac882fc26..3b4cc19c3 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,23 +1,25 @@ // SPDX-License-Identifier: GPL-3.0-or-later pub mod transaction; +pub mod tree; -use std::collections::HashMap; +use std::collections::{hash_map::Entry, HashMap}; +use anyhow::Context; use indexmap::IndexSet; -use pinnacle_api_defs::pinnacle::layout::v0alpha1::{layout_request::Geometries, LayoutResponse}; use smithay::{ desktop::{layer_map_for_output, WindowSurface}, output::Output, utils::{Logical, Rectangle, Serial}, }; use tokio::sync::mpsc::UnboundedSender; -use tonic::Status; use tracing::warn; +use tree::{LayoutNode, LayoutTree}; use crate::{ output::OutputName, state::{Pinnacle, State, WithState}, + tag::TagId, window::{window_state::WindowState, WindowElement}, }; @@ -142,13 +144,23 @@ impl Pinnacle { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct LayoutRequestId(u32); +impl LayoutRequestId { + pub fn to_inner(self) -> u32 { + self.0 + } +} + #[derive(Debug, Default)] pub struct LayoutState { - pub layout_request_sender: Option>>, + pub layout_request_sender: Option>, pub pending_swap: bool, + // TODO: make these outputs weak or something pending_requests: HashMap, fulfilled_requests: HashMap, current_id: LayoutRequestId, + + // TODO: experimenting + pub layout_trees: HashMap, } impl LayoutState { @@ -158,6 +170,14 @@ impl LayoutState { } } +#[derive(Debug)] +pub struct LayoutInfo { + pub request_id: LayoutRequestId, + pub output_name: OutputName, + pub window_count: u32, + pub tag_ids: Vec, +} + impl Pinnacle { pub fn request_layout(&mut self, output: &Output) { if self @@ -186,61 +206,50 @@ impl Pinnacle { .collect::>() }); - let windows = windows_on_foc_tags + let window_count = windows_on_foc_tags .iter() .filter(|win| win.with_state(|state| state.window_state.is_tiled())) - .cloned() - .collect::>(); + .count(); - let (output_width, output_height) = { - let map = layer_map_for_output(output); - let zone = map.non_exclusive_zone(); - (zone.size.w, zone.size.h) - }; - - let window_ids = windows - .iter() - .map(|win| win.with_state(|state| state.id.0)) - .collect::>(); - - let tag_ids = output.with_state(|state| { - state - .focused_tags() - .map(|tag| tag.id().to_inner()) - .collect() - }); + let tag_ids = output.with_state(|state| state.focused_tags().map(|tag| tag.id()).collect()); self.layout_state .pending_requests .insert(output.clone(), id); - let _ = sender.send(Ok(LayoutResponse { - request_id: Some(id.0), - output_name: Some(output.name()), - window_ids, + let _ = sender.send(LayoutInfo { + request_id: id, + output_name: OutputName(output.name()), + window_count: window_count as u32, tag_ids, - output_width: Some(output_width as u32), - output_height: Some(output_height as u32), - })); + }); } } impl State { - pub fn apply_layout(&mut self, geometries: Geometries) -> anyhow::Result<()> { - let Geometries { - request_id: Some(request_id), - output_name: Some(output_name), - geometries, - } = geometries - else { - anyhow::bail!("One or more `geometries` fields were None"); - }; - - let request_id = LayoutRequestId(request_id); + pub fn apply_layout_tree( + &mut self, + tree_id: u32, + tree: LayoutTree, + request_id: u32, + output_name: String, + ) -> anyhow::Result<()> { let Some(output) = OutputName(output_name).output(&self.pinnacle) else { anyhow::bail!("Output was invalid"); }; + let tree_entry = self.pinnacle.layout_state.layout_trees.entry(tree_id); + let tree = match tree_entry { + Entry::Occupied(occupied_entry) => { + let tree_inner = occupied_entry.into_mut(); + tree_inner.diff(tree.root, tree.root_id); + tree_inner + } + Entry::Vacant(vacant_entry) => vacant_entry.insert(tree), + }; + + let request_id = LayoutRequestId(request_id); + let Some(current_pending) = self .pinnacle .layout_state @@ -258,19 +267,18 @@ impl State { anyhow::bail!("Attempted to layout but request is newer"); } - let geometries = geometries - .into_iter() - .map(|geo| { - Some(Rectangle::::from_loc_and_size( - (geo.x?, geo.y?), - (i32::max(geo.width?, 1), i32::max(geo.height?, 1)), - )) - }) - .collect::>>(); - - let Some(geometries) = geometries else { - anyhow::bail!("Attempted to layout but one or more dimensions were null"); - }; + let output_size = self + .pinnacle + .space + .output_geometry(&output) + .context("output has no size")? + .size; + + let geometries = tree + .compute_geos(output_size.w as u32, output_size.h as u32) + .values() + .copied() + .collect(); self.pinnacle.layout_state.pending_requests.remove(&output); self.pinnacle diff --git a/src/layout/tree.rs b/src/layout/tree.rs new file mode 100644 index 000000000..f79ce3250 --- /dev/null +++ b/src/layout/tree.rs @@ -0,0 +1,283 @@ +use std::collections::{hash_map::Entry, BTreeMap, HashMap}; + +use smithay::utils::{Logical, Rectangle}; + +#[derive(PartialEq, Clone, Debug)] +pub struct LayoutNode { + pub style: taffy::Style, + pub children: indexmap::IndexMap, +} + +impl treediff::Value for LayoutNode { + type Key = u32; + + type Item = Self; + + fn items<'a>(&'a self) -> Option + 'a>> { + if self.children.is_empty() { + return None; + } + Some(Box::new(self.children.iter().map(|(id, node)| (*id, node))) as _) + } +} + +#[derive(Debug)] +pub struct LayoutTree { + pub(super) tree: taffy::TaffyTree, + pub(super) id_map: HashMap, + pub(super) root_id: u32, + pub(super) root: LayoutNode, + pub(super) taffy_root_id: taffy::NodeId, + pub(super) inner_gaps: f32, + pub(super) outer_gaps: f32, +} + +impl LayoutTree { + fn build_node( + tree: &mut taffy::TaffyTree, + node: LayoutNode, + node_id: u32, + id_map: &mut HashMap, + inner_gaps: f32, + ) -> taffy::NodeId { + let children = node + .children + .into_iter() + .map(|(child_id, child)| Self::build_node(tree, child, child_id, id_map, inner_gaps)) + .collect::>(); + + let root_id = match id_map.entry(node_id) { + Entry::Occupied(occupied_entry) => { + let id = *occupied_entry.get(); + tree.set_style(id, node.style).unwrap(); + tree.set_children(id, &children).unwrap(); + tree.set_node_context(id, Some(node_id)).unwrap(); + id + } + Entry::Vacant(vacant_entry) => { + let id = tree.new_with_children(node.style, &children).unwrap(); + tree.set_node_context(id, Some(node_id)).unwrap(); + vacant_entry.insert(id); + id + } + }; + + let has_children = !children.is_empty(); + if !has_children { + let leaf_child = tree + .new_leaf_with_context( + taffy::Style { + margin: taffy::Rect::length(inner_gaps), + flex_basis: taffy::Dimension::Percent(1.0), + ..Default::default() + }, + node_id, + ) + .unwrap(); + tree.set_children(root_id, &[leaf_child]).unwrap(); + } + + root_id + } + + pub fn new(root: LayoutNode, root_id: u32, inner_gaps: f32, outer_gaps: f32) -> Self { + let tree = taffy::TaffyTree::::new(); + let id_map = HashMap::new(); + + Self::new_with_data(root, root_id, tree, id_map, inner_gaps, outer_gaps) + } + + fn new_with_data( + root: LayoutNode, + root_id: u32, + mut tree: taffy::TaffyTree, + mut id_map: HashMap, + inner_gaps: f32, + outer_gaps: f32, + ) -> Self { + let taffy_root_id = + Self::build_node(&mut tree, root.clone(), root_id, &mut id_map, inner_gaps); + let mut root_style = tree.style(taffy_root_id).unwrap().clone(); + root_style.size = taffy::Size { + width: taffy::Dimension::Percent(1.0), + height: taffy::Dimension::Percent(1.0), + }; + root_style.padding = taffy::Rect::length(outer_gaps); + tree.set_style(taffy_root_id, root_style).unwrap(); + + Self { + tree, + id_map, + root_id, + root, + taffy_root_id, + inner_gaps, + outer_gaps, + } + } + + pub fn compute_geos( + &mut self, + width: u32, + height: u32, + ) -> BTreeMap> { + self.tree + .compute_layout( + self.taffy_root_id, + taffy::Size { + width: taffy::AvailableSpace::Definite(width as f32), + height: taffy::AvailableSpace::Definite(height as f32), + }, + ) + .unwrap(); + + self.tree.print_tree(self.taffy_root_id); + + let mut geos = BTreeMap::>::new(); + + fn compute_geos_rec( + geos: &mut BTreeMap>, + tree: &taffy::TaffyTree, + node: taffy::NodeId, + offset_x: f64, + offset_y: f64, + ) { + let geo = tree.layout(node).unwrap(); + let mut loc = geo.location.map(|loc| loc as f64); + loc.x += offset_x; + loc.y += offset_y; + let size = geo.size.map(|size| size as f64); + + let index = *tree.get_node_context(node).unwrap(); + + let children = tree.children(node).unwrap(); + + if children.is_empty() { + let rect: Rectangle = Rectangle { + loc: smithay::utils::Point::from((loc.x, loc.y)), + size: smithay::utils::Size::from((size.width, size.height)), + } + .to_i32_round(); + geos.insert(index, rect); + return; + } + + for child in children.into_iter() { + compute_geos_rec(geos, tree, child, loc.x, loc.y); + } + } + + compute_geos_rec(&mut geos, &self.tree, self.taffy_root_id, 0.0, 0.0); + + geos + } + + pub fn diff(&mut self, root: LayoutNode, root_id: u32) { + for node in self.id_map.values().copied() { + self.tree.set_children(node, &[]).unwrap(); + } + let tree = std::mem::replace(&mut self.tree, taffy::TaffyTree::new()); + let id_map = std::mem::take(&mut self.id_map); + *self = Self::new_with_data( + root, + root_id, + tree, + id_map, + self.inner_gaps, + self.outer_gaps, + ); + + // if root_id != self.root_id { + // // If the root node's id has changed we're just gonna make a whole new tree + // *self = Self::new(root, root_id); + // return; + // } + // + // if self.root.style != root.style { + // let mut new_root_style = root.style.clone(); + // new_root_style.size = taffy::Size { + // width: taffy::Dimension::Percent(1.0), + // height: taffy::Dimension::Percent(1.0), + // }; + // self.tree + // .set_style(self.taffy_root_id, new_root_style) + // .unwrap(); + // } + // + // let mut d = treediff::tools::Recorder::default(); + // treediff::diff(&self.root, &root, &mut d); + // + // dbg!(&d.calls); + // + // for call in d.calls { + // match call { + // treediff::tools::ChangeType::Removed(vec, _node) => { + // let last_node = *self.id_map.get(vec.last().unwrap()).unwrap(); + // let parent = vec + // .iter() + // .nth_back(1) + // .map(|id| *self.id_map.get(id).unwrap()) + // .unwrap_or(self.taffy_root_id); + // self.tree.remove_child(parent, last_node).unwrap(); + // } + // treediff::tools::ChangeType::Added(mut vec, node) => { + // let mut parent_node = &root; + // + // let new_node_id = vec.pop().unwrap(); + // + // for id in vec.iter() { + // parent_node = parent_node.children.get(id).unwrap(); + // } + // + // let parent = vec + // .last() + // .map(|id| *self.id_map.get(id).unwrap()) + // .unwrap_or(self.taffy_root_id); + // let child = Self::build_node( + // &mut self.tree, + // node.clone(), + // new_node_id, + // &mut self.id_map, + // ); + // + // let insert_index = parent_node.children.get_index_of(&new_node_id).unwrap(); + // + // self.tree + // .insert_child_at_index(parent, insert_index, child) + // .unwrap(); + // } + // treediff::tools::ChangeType::Unchanged(..) => (), + // treediff::tools::ChangeType::Modified(vec, _old_node, new_node) => { + // match vec.last().copied() { + // Some(last) => { + // Self::build_node( + // &mut self.tree, + // new_node.clone(), + // last, + // &mut self.id_map, + // ); + // } + // None => { + // Self::build_node( + // &mut self.tree, + // new_node.clone(), + // self.root_id, + // &mut self.id_map, + // ); + // + // let mut root_style = + // self.tree.style(self.taffy_root_id).unwrap().clone(); + // root_style.size = taffy::Size { + // width: taffy::Dimension::Percent(1.0), + // height: taffy::Dimension::Percent(1.0), + // }; + // self.tree.set_style(self.taffy_root_id, root_style).unwrap(); + // } + // } + // } + // } + // } + // + // self.root = root; + } +} diff --git a/src/main.rs b/src/main.rs index c724ce643..e8766ef4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,222 @@ use xdg::BaseDirectories; #[tokio::main] async fn main() -> anyhow::Result<()> { + // FIXME: remove + // let layout1 = pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: [ + // ( + // 1, + // pinnacle::layout::LayoutNode { + // row: false, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 2, + // pinnacle::layout::LayoutNode { + // row: false, + // flex_grow: 1.0, + // children: [ + // ( + // 3, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 4, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 5, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 6, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ] + // .into(), + // }, + // ), + // ] + // .into(), + // }; + // + // let layout2 = pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: [ + // ( + // 1, + // pinnacle::layout::LayoutNode { + // row: false, + // flex_grow: 1.0, + // children: [ + // ( + // 3, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 4, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ] + // .into(), + // }, + // ), + // ( + // 7, + // pinnacle::layout::LayoutNode { + // row: false, + // flex_grow: 1.0, + // children: [ + // ( + // 5, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 6, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 2, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ] + // .into(), + // }, + // ), + // ] + // .into(), + // }; + // + // let layout3 = pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: [ + // ( + // 1, + // pinnacle::layout::LayoutNode { + // row: false, + // flex_grow: 1.0, + // children: [ + // ( + // 3, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 2.0, + // children: Default::default(), + // }, + // ), + // ( + // 4, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 11, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ] + // .into(), + // }, + // ), + // ( + // 7, + // pinnacle::layout::LayoutNode { + // row: false, + // flex_grow: 1.0, + // children: [ + // ( + // 5, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 6, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ( + // 2, + // pinnacle::layout::LayoutNode { + // row: true, + // flex_grow: 1.0, + // children: Default::default(), + // }, + // ), + // ] + // .into(), + // }, + // ), + // ] + // .into(), + // }; + // + // let mut tree = pinnacle::layout::LayoutTree::new(layout1, 0); + // tree.compute_geos(); + // + // println!("diffing"); + // tree.diff(layout2, 0); + // tree.compute_geos(); + // + // println!("diffing again"); + // tree.diff(layout3, 0); + // dbg!(tree.compute_geos()); + // + // return Ok(()); + let base_dirs = BaseDirectories::with_prefix("pinnacle")?; let xdg_state_dir = base_dirs.get_state_home();