From e0c0a2d1668efe5817cbda77485c73c8b075b8f9 Mon Sep 17 00:00:00 2001 From: Cody Bloemhard Date: Thu, 15 Aug 2024 07:55:22 +0000 Subject: [PATCH] feat: toggle_floating_state and is_floating (#306) (#307) * feat: toggle_floating_state and is_floating (#306) * fix: remove unnecessary mut; * fix: apply cargo fmt; * fix: remove mutable borrow from is_floating; refactor: toggle_floating_state; testcase: floating_client_status tests is_floating; * test: toggle_floating_state * fix: apply cargo fmt; --- src/builtin/actions/floating.rs | 20 ++++++++- src/pure/stack_set.rs | 79 +++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/builtin/actions/floating.rs b/src/builtin/actions/floating.rs index caa0b530..7190f8e1 100644 --- a/src/builtin/actions/floating.rs +++ b/src/builtin/actions/floating.rs @@ -52,7 +52,7 @@ pub fn reposition(dx: i32, dy: i32) -> Box> { }) } -/// Move the currently focused windo to the floating layer in its current on screen position +/// Move the currently focused window to the floating layer in its current on screen position pub fn float_focused() -> Box> { key_handler(|state, x: &X| { let id = match state.client_set.current_client() { @@ -82,6 +82,24 @@ pub fn sink_focused() -> Box> { }) } +/// Sink the current window if it was floating, float it if it was tiled. +pub fn toggle_floating_focused() -> Box> { + key_handler(|state, x: &X| { + let id = match state.client_set.current_client() { + Some(&id) => id, + None => return Ok(()), + }; + + let r = x.client_geometry(id)?; + + x.modify_and_refresh(state, |cs| { + if let Err(err) = cs.toggle_floating_state(id, r) { + error!(%err, %id, "unable to float requested client window"); + } + }) + }) +} + /// Float all windows in their current tiled position pub fn float_all() -> Box> { key_handler(|state, x: &X| { diff --git a/src/pure/stack_set.rs b/src/pure/stack_set.rs index 6f9c2a75..52dda1e2 100644 --- a/src/pure/stack_set.rs +++ b/src/pure/stack_set.rs @@ -270,6 +270,11 @@ where .map(|rr| rr.applied_to(&self.screens.focus.r)) } + /// Check whether a given client is currently floating. + pub fn is_floating(&self, client: &C) -> bool { + self.floating.contains_key(client) + } + /// Check whether a given tag currently has any floating windows present. /// /// Returns false if the tag given is unknown to this StackSet. @@ -799,6 +804,27 @@ impl StackSet { Ok(()) } + /// If a known client is floating, sink it and return its previous preferred screen position. + /// Otherwise, record it as floating with its preferred screen position. + /// + /// # Errors + /// This method with return [Error::UnknownClient] if the given client is + /// not already managed in this stack_set. + /// + /// This method with return [Error::ClientIsNotVisible] if the given client is + /// not currently mapped to a screen. This is required to determine the correct + /// relative positioning for the floating client as is it is moved between + /// screens. + pub fn toggle_floating_state(&mut self, client: Xid, r: Rect) -> Result> { + let rect = if self.is_floating(&client) { + self.sink(&client) + } else { + self.float(client, r)?; + None + }; + Ok(rect) + } + pub(crate) fn update_screens(&mut self, rects: Vec) -> Result<()> { let n_old = self.screens.len(); let n_new = rects.len(); @@ -1280,6 +1306,59 @@ pub mod tests { assert_eq!(s.current_client(), Some(&4)); } + #[test_case(&[]; "none")] + #[test_case(&[1]; "one")] + #[test_case(&[1, 2, 4]; "multiple")] + #[test] + fn floating_client_status(to_float: &[u8]) { + let mut s = test_stack_set(5, 3); + for n in 1..5 { + s.insert(n); + } + + for c in to_float { + s.float_unchecked(*c, Rect::default()); + } + for c in to_float { + assert!(s.is_floating(c)); + } + for client in s.clients().copied() { + assert_eq!(to_float.contains(&client), s.is_floating(&client)); + } + } + + #[test] + fn toggle_floating_state() { + let mut ss: StackSet = StackSet::try_new( + LayoutStack::default(), + ["1", "2", "3"], + vec![Rect::default(); 2], + ) + .expect("enough workspaces to cover the number of initial screens"); + + ss.insert(Xid(0)); + ss.insert(Xid(1)); + + for i in 0..10 { + assert_eq!(ss.is_floating(&Xid(0)), i % 2 != 0); + let res = ss.toggle_floating_state(Xid(0), Rect::default()); + assert!(res.is_ok()); + } + + assert!(matches!( + ss.toggle_floating_state(Xid(3), Rect::default()), + Err(Error::UnknownClient(_)) + )); + + ss.insert(Xid(3)); + ss.move_client_to_tag(&Xid(3), "3"); + + assert!(matches!( + ss.toggle_floating_state(Xid(3), Rect::default()), + Err(Error::ClientIsNotVisible(_)) + )); + } + #[test_case(1, "1"; "current focus to current tag")] #[test_case(2, "1"; "from current tag to current tag")] #[test_case(6, "1"; "from other tag to current tag")]