From 6416c0b6eb13dd779f0e97e9b72d542e5193ba97 Mon Sep 17 00:00:00 2001 From: JustForFun88 Date: Sun, 15 Jun 2025 13:58:33 +0500 Subject: [PATCH] refactor(wm): move 'locked' flag down to containers Key Changes - Added `locked: bool` field directly to the `Container` struct. - Removed `locked_containers` from `Workspace`. - Updated `komorebi_bar` to access `locked` directly from `Container`. Insert and swap operations respects `locked` container indexes in the sequence --- komorebi-bar/src/widgets/komorebi.rs | 9 +- komorebi/src/border_manager/mod.rs | 4 +- komorebi/src/container.rs | 57 ++++- komorebi/src/lib.rs | 10 +- komorebi/src/lockable_sequence.rs | 357 +++++++++++++++++++++++++++ komorebi/src/locked_deque.rs | 316 ------------------------ komorebi/src/process_command.rs | 8 +- komorebi/src/ring.rs | 4 - komorebi/src/window_manager.rs | 18 +- komorebi/src/workspace.rs | 60 ++--- 10 files changed, 462 insertions(+), 381 deletions(-) create mode 100644 komorebi/src/lockable_sequence.rs delete mode 100644 komorebi/src/locked_deque.rs diff --git a/komorebi-bar/src/widgets/komorebi.rs b/komorebi-bar/src/widgets/komorebi.rs index 31541a13..db050654 100644 --- a/komorebi-bar/src/widgets/komorebi.rs +++ b/komorebi-bar/src/widgets/komorebi.rs @@ -846,11 +846,12 @@ impl KomorebiNotificationState { } let focused_workspace = &monitor.workspaces()[focused_workspace_idx]; - let is_focused = focused_workspace - .locked_containers() - .contains(&focused_workspace.focused_container_idx()); + let is_locked = match focused_workspace.focused_container() { + Some(container) => container.locked(), + None => false, + }; - self.focused_container_information = (is_focused, focused_workspace.into()); + self.focused_container_information = (is_locked, focused_workspace.into()); } } diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index 20db7dd1..749e425e 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -255,7 +255,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result let window_kind = if idx != ws.focused_container_idx() || monitor_idx != focused_monitor_idx { - if ws.locked_containers().contains(&idx) { + if c.locked() { WindowKind::UnfocusedLocked } else { WindowKind::Unfocused @@ -563,7 +563,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result || monitor_idx != focused_monitor_idx || focused_window_hwnd != foreground_window { - if ws.locked_containers().contains(&idx) { + if c.locked() { WindowKind::UnfocusedLocked } else { WindowKind::Unfocused diff --git a/komorebi/src/container.rs b/komorebi/src/container.rs index abce0882..f74f525e 100644 --- a/komorebi/src/container.rs +++ b/komorebi/src/container.rs @@ -1,18 +1,24 @@ use std::collections::VecDeque; +use getset::CopyGetters; use getset::Getters; +use getset::Setters; use nanoid::nanoid; use serde::Deserialize; use serde::Serialize; use crate::ring::Ring; use crate::window::Window; +use crate::Lockable; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters, CopyGetters, Setters)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Container { #[getset(get = "pub")] id: String, + #[serde(default)] + #[getset(get_copy = "pub", set = "pub")] + locked: bool, windows: Ring, } @@ -22,11 +28,23 @@ impl Default for Container { fn default() -> Self { Self { id: nanoid!(), + locked: false, windows: Ring::default(), } } } +impl Lockable for Container { + fn locked(&self) -> bool { + self.locked + } + + fn set_locked(&mut self, locked: bool) -> &mut Self { + self.locked = locked; + self + } +} + impl Container { pub fn hide(&self, omit: Option) { for window in self.windows().iter().rev() { @@ -144,6 +162,7 @@ impl Container { #[cfg(test)] mod tests { use super::*; + use serde_json; #[test] fn test_contains_window() { @@ -250,4 +269,40 @@ mod tests { // Should return None since window 4 doesn't exist assert_eq!(container.idx_for_window(4), None); } + + #[test] + fn deserializes_with_missing_locked_field_defaults_to_false() { + let json = r#"{ + "id": "test-1", + "windows": { "elements": [], "focused": 0 } + }"#; + let container: Container = serde_json::from_str(json).expect("Should deserialize"); + + assert!(!container.locked()); + assert_eq!(container.id(), "test-1"); + assert!(container.windows().is_empty()); + + let json = r#"{ + "id": "test-2", + "windows": { "elements": [ { "hwnd": 5 }, { "hwnd": 9 } ], "focused": 1 } + }"#; + let container: Container = serde_json::from_str(json).unwrap(); + assert_eq!(container.id(), "test-2"); + assert!(!container.locked()); + assert_eq!(container.windows(), &[Window::from(5), Window::from(9)]); + assert_eq!(container.focused_window_idx(), 1); + } + + #[test] + fn serializes_and_deserializes() { + let mut container = Container::default(); + container.set_locked(true); + + let serialized = serde_json::to_string(&container).expect("Should serialize"); + let deserialized: Container = + serde_json::from_str(&serialized).expect("Should deserialize"); + + assert_eq!(deserialized.locked(), true); + assert_eq!(deserialized.id(), container.id()); + } } diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index 323d1eef..ebcf5769 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -8,7 +8,7 @@ pub mod ring; pub mod container; pub mod core; pub mod focus_manager; -pub mod locked_deque; +pub mod lockable_sequence; pub mod monitor; pub mod monitor_reconciliator; pub mod process_command; @@ -255,6 +255,14 @@ pub static WINDOW_HANDLING_BEHAVIOUR: AtomicCell = shadow_rs::shadow!(build); +/// A trait for types that can be marked as locked or unlocked. +pub trait Lockable { + /// Returns `true` if the item is locked. + fn locked(&self) -> bool; + /// Sets the locked state of the item. + fn set_locked(&mut self, locked: bool) -> &mut Self; +} + #[must_use] pub fn current_virtual_desktop() -> Option> { let hkcu = RegKey::predef(HKEY_CURRENT_USER); diff --git a/komorebi/src/lockable_sequence.rs b/komorebi/src/lockable_sequence.rs new file mode 100644 index 00000000..4564895d --- /dev/null +++ b/komorebi/src/lockable_sequence.rs @@ -0,0 +1,357 @@ +use std::collections::VecDeque; + +use crate::Lockable; + +/// A sequence supporting insertion, removal, and swapping of elements while preserving the absolute +/// positions of locked items. +pub trait LockableSequence { + /// Inserts a value at `idx`, keeping locked elements at their absolute positions. + fn insert_respecting_locks(&mut self, idx: usize, value: T) -> usize; + /// Removes the element at `idx`, keeping locked elements at their absolute positions. + fn remove_respecting_locks(&mut self, idx: usize) -> Option; + /// Swaps the elements at indices `i` and `j`, keeping locked elements at their absolute positions. + fn swap_respecting_locks(&mut self, i: usize, j: usize); +} + +impl LockableSequence for VecDeque { + /// Insert `value` at logical index `idx`, with trying to keep locked elements + /// (`is_locked()`) anchored at their original positions. + /// + /// Returns the final index of the inserted element. + fn insert_respecting_locks(&mut self, mut idx: usize, value: T) -> usize { + // 1. Bounds check: if index is out of range, simply append. + if idx >= self.len() { + self.push_back(value); + return self.len() - 1; // last index + } + + // 2. Normal VecDeque insertion + self.insert(idx, value); + + // 3. Walk left-to-right once, swapping any misplaced locked element. After + // the VecDeque::insert all items after `idx` have moved right by one. For every locked + // element that is now to the right of an unlocked one, swap it back left exactly once. + for index in (idx + 1)..self.len() { + if self[index].locked() && !self[index - 1].locked() { + self.swap(index - 1, index); + + // If the element we just inserted participated in the swap, + // update `idx` so we can return its final location. + if idx == index - 1 { + idx = index; + } + } + } + idx + } + + /// Remove element at `idx`, with trying to keep locked elements + /// (`is_locked()`) anchored at their original positions. + /// + /// Returns the removed element, or `None` if `idx` is out of bounds. + fn remove_respecting_locks(&mut self, idx: usize) -> Option { + // 1. Bounds check: if index is out of range, do nothing. + if idx >= self.len() { + return None; + } + + // 2. Remove the element at the requested index. + // All elements after idx are now shifted left by 1. + let removed = self.remove(idx)?; + + // 3. If less than 2 elements remain, nothing to shift. + if self.len() < 2 { + return Some(removed); + } + + // 4. Iterate from the element just after the removed spot up to the second-to-last + // element, right-to-left. This loop "fixes" locked elements that were shifted left + // off their anchored positions: If a locked element now has an unlocked element + // to its right, swap them back to restore locked order. + for index in (idx..self.len() - 1).rev() { + // If current is locked and the next one is not locked, swap them. + if self[index].locked() && !self[index + 1].locked() { + self.swap(index, index + 1); + } + } + + // 5. Return the removed value. + Some(removed) + } + + /// Swaps the elements at indices `i` and `j`, along with their `locked` status, ensuring + /// the lock state remains associated with the position rather than the element itself. + fn swap_respecting_locks(&mut self, i: usize, j: usize) { + self.swap(i, j); + let locked_i = self[i].locked(); + let locked_j = self[j].locked(); + self[i].set_locked(locked_j); + self[j].set_locked(locked_i); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, PartialEq)] + struct TestItem { + val: i32, + locked: bool, + } + + impl Lockable for TestItem { + fn locked(&self) -> bool { + self.locked + } + + fn set_locked(&mut self, locked: bool) -> &mut Self { + self.locked = locked; + self + } + } + + fn vals(v: &VecDeque) -> Vec { + v.iter().map(|x| x.val).collect() + } + + fn test_deque(items: &[(i32, bool)]) -> VecDeque { + items + .iter() + .cloned() + .map(|(val, locked)| TestItem { val, locked }) + .collect() + } + + #[test] + fn test_insert_respecting_locks() { + // Test case 1: Basic insertion with locked index + { + // Lock index 2 + let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]); + // Insert at index 0, should shift elements while keeping index 2 locked + ring.insert_respecting_locks( + 0, + TestItem { + val: 99, + locked: false, + }, + ); + // Element '2' remains at index 2, element '1' that was at index 1 is now at index 3 + assert_eq!(vals(&ring), vec![99, 0, 2, 1, 3, 4]); + } + + // Test case 2: Insert at a locked index (should insert after locked) + { + // Lock index 2 + let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]); + // Try to insert at locked index 2, should insert at index 3 instead + let actual_index = ring.insert_respecting_locks( + 2, + TestItem { + val: 99, + locked: false, + }, + ); + assert_eq!(actual_index, 3); + assert_eq!(vals(&ring), vec![0, 1, 2, 99, 3, 4]); + } + + // Test case 3: Multiple locked indices + { + // Lock index 1 and 3 + let mut ring = test_deque(&[(0, false), (1, true), (2, false), (3, true), (4, false)]); + // Insert at index 0, should maintain locked indices + ring.insert_respecting_locks( + 0, + TestItem { + val: 99, + locked: false, + }, + ); + // Elements '1' and '3' remain at indices 1 and 3 + assert_eq!(vals(&ring), vec![99, 1, 0, 3, 2, 4]); + } + + // Test case 4: Insert at end + { + // Lock index 2 + let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]); + let actual_index = ring.insert_respecting_locks( + 5, + TestItem { + val: 99, + locked: false, + }, + ); + assert_eq!(actual_index, 5); + assert_eq!(vals(&ring), vec![0, 1, 2, 3, 4, 99]); + } + + // Test case 5: Empty ring + { + let mut ring = test_deque(&[]); + // Insert into empty deque + let actual_index = ring.insert_respecting_locks( + 0, + TestItem { + val: 99, + locked: false, + }, + ); + assert_eq!(actual_index, 0); + assert_eq!(vals(&ring), vec![99]); + } + + // Test case 6: All indices locked + { + // Lock all indices + let mut ring = test_deque(&[(0, true), (1, true), (2, true), (3, true), (4, true)]); + // Try to insert at index 2, should insert at the end + let actual_index = ring.insert_respecting_locks( + 2, + TestItem { + val: 99, + locked: false, + }, + ); + assert_eq!(actual_index, 5); + assert_eq!(vals(&ring), vec![0, 1, 2, 3, 4, 99]); + } + + // Test case 7: Consecutive locked indices + { + // Lock index 2 and 3 + let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, true), (4, false)]); + // Insert at index 1, should maintain consecutive locked indices + ring.insert_respecting_locks( + 1, + TestItem { + val: 99, + locked: false, + }, + ); + // Elements '2' and '3' remain at indices 2 and 3 + assert_eq!(vals(&ring), vec![0, 99, 2, 3, 1, 4]); + } + } + + #[test] + fn test_remove_respecting_locks() { + // Test case 1: Remove a non-locked index before a locked index + { + // Lock index 2 + let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]); + let removed = ring.remove_respecting_locks(0); + assert_eq!(removed.map(|x| x.val), Some(0)); + // Elements '2' remain at index 2 + assert_eq!(vals(&ring), vec![1, 3, 2, 4]); + } + + // Test case 2: Remove a locked index + { + // Lock index 2 + let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]); + let removed = ring.remove_respecting_locks(2); + assert_eq!(removed.map(|x| x.val), Some(2)); + // Elements should stay at the same places + assert_eq!(vals(&ring), vec![0, 1, 3, 4]); + } + + // Test case 3: Remove an index after a locked index + { + // Lock index 1 + let mut ring = test_deque(&[(0, false), (1, true), (2, false), (3, false), (4, false)]); + let removed = ring.remove_respecting_locks(3); + assert_eq!(removed.map(|x| x.val), Some(3)); + // Elements should stay at the same places + assert_eq!(vals(&ring), vec![0, 1, 2, 4]); + } + + // Test case 4: Multiple locked indices + { + // Lock index 1 and 3 + let mut ring = test_deque(&[(0, false), (1, true), (2, false), (3, true), (4, false)]); + let removed = ring.remove_respecting_locks(0); + assert_eq!(removed.map(|x| x.val), Some(0)); + // Elements '1' and '3' remain at indices '1' and '3' + assert_eq!(vals(&ring), vec![2, 1, 4, 3]); + } + + // Test case 5: Remove the last element + { + // Lock index 2 + let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]); + let removed = ring.remove_respecting_locks(4); + assert_eq!(removed.map(|x| x.val), Some(4)); + // Index 2 should still be at the same place + assert_eq!(vals(&ring), vec![0, 1, 2, 3]); + } + + // Test case 6: Invalid index + { + // Lock index 2 + let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]); + let removed = ring.remove_respecting_locks(10); + assert_eq!(removed, None); + // Deque unchanged + assert_eq!(vals(&ring), vec![0, 1, 2, 3, 4]); + } + + // Test case 7: Remove enough elements to make a locked index invalid + { + // Lock index 2 + let mut ring = test_deque(&[(0, false), (1, false), (2, true)]); + ring.remove_respecting_locks(0); + // Index 2 should now be '1' + assert_eq!(vals(&ring), vec![1, 2]); + } + + // Test case 8: Removing an element before multiple locked indices + { + // Lock index 2 and 4 + let mut ring = test_deque(&[ + (0, false), + (1, false), + (2, true), + (3, false), + (4, true), + (5, false), + ]); + let removed = ring.remove_respecting_locks(1); + assert_eq!(removed.map(|x| x.val), Some(1)); + // Both indices should still be at the same place + assert_eq!(vals(&ring), vec![0, 3, 2, 5, 4]); + } + } + + #[test] + fn test_swap_respecting_locks_various_cases() { + // Swap unlocked and locked + let mut ring = test_deque(&[(0, false), (1, true), (2, false), (3, false)]); + ring.swap_respecting_locks(0, 1); + assert_eq!(vals(&ring), vec![1, 0, 2, 3]); + assert_eq!(ring[0].locked, false); + assert_eq!(ring[1].locked, true); + ring.swap_respecting_locks(0, 1); + assert_eq!(vals(&ring), vec![0, 1, 2, 3]); + assert_eq!(ring[0].locked, false); + assert_eq!(ring[1].locked, true); + + // Both locked + let mut ring = test_deque(&[(0, true), (1, false), (2, true)]); + ring.swap_respecting_locks(0, 2); + assert_eq!(vals(&ring), vec![2, 1, 0]); + assert!(ring[0].locked); + assert!(!ring[1].locked); + assert!(ring[2].locked); + + // Both unlocked + let mut ring = test_deque(&[(0, false), (1, true), (2, false)]); + ring.swap_respecting_locks(0, 2); + assert_eq!(vals(&ring), vec![2, 1, 0]); + assert!(!ring[0].locked); + assert!(ring[1].locked); + assert!(!ring[2].locked); + } +} diff --git a/komorebi/src/locked_deque.rs b/komorebi/src/locked_deque.rs deleted file mode 100644 index ed98640a..00000000 --- a/komorebi/src/locked_deque.rs +++ /dev/null @@ -1,316 +0,0 @@ -use std::collections::BTreeSet; -use std::collections::VecDeque; - -pub struct LockedDeque<'a, T> { - deque: &'a mut VecDeque, - locked_indices: &'a mut BTreeSet, -} - -impl<'a, T: PartialEq> LockedDeque<'a, T> { - pub fn new(deque: &'a mut VecDeque, locked_indices: &'a mut BTreeSet) -> Self { - Self { - deque, - locked_indices, - } - } - - pub fn insert(&mut self, index: usize, value: T) -> usize { - insert_respecting_locks(self.deque, self.locked_indices, index, value) - } - - pub fn remove(&mut self, index: usize) -> Option { - remove_respecting_locks(self.deque, self.locked_indices, index) - } -} - -pub fn insert_respecting_locks( - deque: &mut VecDeque, - locked_idx: &mut BTreeSet, - idx: usize, - value: T, -) -> usize { - if idx == deque.len() { - deque.push_back(value); - return idx; - } - - let mut new_deque = VecDeque::with_capacity(deque.len() + 1); - let mut temp_locked_deque = VecDeque::new(); - let mut j = 0; - let mut corrected_idx = idx; - - for (i, el) in deque.drain(..).enumerate() { - if i == idx { - corrected_idx = j; - } - if locked_idx.contains(&i) { - temp_locked_deque.push_back(el); - } else { - new_deque.push_back(el); - j += 1; - } - } - - new_deque.insert(corrected_idx, value); - - for (locked_el, locked_idx) in temp_locked_deque.into_iter().zip(locked_idx.iter()) { - new_deque.insert(*locked_idx, locked_el); - if *locked_idx <= corrected_idx { - corrected_idx += 1; - } - } - - *deque = new_deque; - - corrected_idx -} - -pub fn remove_respecting_locks( - deque: &mut VecDeque, - locked_idx: &mut BTreeSet, - idx: usize, -) -> Option { - if idx >= deque.len() { - return None; - } - - let final_size = deque.len() - 1; - - let mut new_deque = VecDeque::with_capacity(final_size); - let mut temp_locked_deque = VecDeque::new(); - let mut removed = None; - let mut removed_locked_idx = None; - - for (i, el) in deque.drain(..).enumerate() { - if i == idx { - removed = Some(el); - removed_locked_idx = locked_idx.contains(&i).then_some(i); - } else if locked_idx.contains(&i) { - temp_locked_deque.push_back(el); - } else { - new_deque.push_back(el); - } - } - - if let Some(i) = removed_locked_idx { - let mut above = locked_idx.split_off(&i); - above.pop_first(); - locked_idx.extend(above.into_iter().map(|i| i - 1)); - } - - while locked_idx.last().is_some_and(|i| *i >= final_size) { - locked_idx.pop_last(); - } - - let extra_invalid_idx = (new_deque.len() - ..(new_deque.len() + temp_locked_deque.len() - locked_idx.len())) - .collect::>(); - - for (locked_el, locked_idx) in temp_locked_deque - .into_iter() - .zip(locked_idx.iter().chain(extra_invalid_idx.iter())) - { - new_deque.insert(*locked_idx, locked_el); - } - - *deque = new_deque; - - removed -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::BTreeSet; - use std::collections::VecDeque; - - #[test] - fn test_insert_respecting_locks() { - // Test case 1: Basic insertion with locked index - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - - // Insert at index 0, should shift elements while keeping index 2 locked - insert_respecting_locks(&mut deque, &mut locked, 0, 99); - assert_eq!(deque, VecDeque::from(vec![99, 0, 2, 1, 3, 4])); - // Element '2' remains at index 2, element '1' that was at index 1 is now at index 3 - } - - // Test case 2: Insert at a locked index - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - - // Try to insert at locked index 2, should insert at index 3 instead - let actual_index = insert_respecting_locks(&mut deque, &mut locked, 2, 99); - assert_eq!(actual_index, 3); - assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 99, 3, 4])); - } - - // Test case 3: Multiple locked indices - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(1); // Lock index 1 - locked.insert(3); // Lock index 3 - - // Insert at index 0, should maintain locked indices - insert_respecting_locks(&mut deque, &mut locked, 0, 99); - assert_eq!(deque, VecDeque::from(vec![99, 1, 0, 3, 2, 4])); - // Elements '1' and '3' remain at indices 1 and 3 - } - - // Test case 4: Insert at end - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - - // Insert at end of deque - let actual_index = insert_respecting_locks(&mut deque, &mut locked, 5, 99); - assert_eq!(actual_index, 5); - assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4, 99])); - } - - // Test case 5: Empty deque - { - let mut deque = VecDeque::new(); - let mut locked = BTreeSet::new(); - - // Insert into empty deque - let actual_index = insert_respecting_locks(&mut deque, &mut locked, 0, 99); - assert_eq!(actual_index, 0); - assert_eq!(deque, VecDeque::from(vec![99])); - } - - // Test case 6: All indices locked - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - for i in 0..5 { - locked.insert(i); // Lock all indices - } - - // Try to insert at index 2, should insert at the end - let actual_index = insert_respecting_locks(&mut deque, &mut locked, 2, 99); - assert_eq!(actual_index, 5); - assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4, 99])); - } - - // Test case 7: Consecutive locked indices - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - locked.insert(3); // Lock index 3 - - // Insert at index 1, should maintain consecutive locked indices - insert_respecting_locks(&mut deque, &mut locked, 1, 99); - assert_eq!(deque, VecDeque::from(vec![0, 99, 2, 3, 1, 4])); - // Elements '2' and '3' remain at indices 2 and 3 - } - } - - #[test] - fn test_remove_respecting_locks() { - // Test case 1: Remove a non-locked index before a locked index - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - - let removed = remove_respecting_locks(&mut deque, &mut locked, 0); - assert_eq!(removed, Some(0)); - assert_eq!(deque, VecDeque::from(vec![1, 3, 2, 4])); - assert!(locked.contains(&2)); // Index 2 should still be locked - } - - // Test case 2: Remove a locked index - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - - let removed = remove_respecting_locks(&mut deque, &mut locked, 2); - assert_eq!(removed, Some(2)); - assert_eq!(deque, VecDeque::from(vec![0, 1, 3, 4])); - assert!(!locked.contains(&2)); // Index 2 should be unlocked - } - - // Test case 3: Remove an index after a locked index - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(1); // Lock index 1 - - let removed = remove_respecting_locks(&mut deque, &mut locked, 3); - assert_eq!(removed, Some(3)); - assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 4])); - assert!(locked.contains(&1)); // Index 1 should still be locked - } - - // Test case 4: Multiple locked indices - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(1); // Lock index 1 - locked.insert(3); // Lock index 3 - - let removed = remove_respecting_locks(&mut deque, &mut locked, 0); - assert_eq!(removed, Some(0)); - assert_eq!(deque, VecDeque::from(vec![2, 1, 4, 3])); - assert!(locked.contains(&1) && locked.contains(&3)); // Both indices should still be locked - } - - // Test case 5: Remove the last element - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - - let removed = remove_respecting_locks(&mut deque, &mut locked, 4); - assert_eq!(removed, Some(4)); - assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3])); - assert!(locked.contains(&2)); // Index 2 should still be locked - } - - // Test case 6: Invalid index - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - - let removed = remove_respecting_locks(&mut deque, &mut locked, 10); - assert_eq!(removed, None); - assert_eq!(deque, VecDeque::from(vec![0, 1, 2, 3, 4])); // Deque unchanged - assert!(locked.contains(&2)); // Lock unchanged - } - - // Test case 7: Remove enough elements to make a locked index invalid - { - let mut deque = VecDeque::from(vec![0, 1, 2]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - - remove_respecting_locks(&mut deque, &mut locked, 0); - assert_eq!(deque, VecDeque::from(vec![1, 2])); - assert!(!locked.contains(&2)); // Index 2 should now be invalid - } - - // Test case 8: Removing an element before multiple locked indices - { - let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4, 5]); - let mut locked = BTreeSet::new(); - locked.insert(2); // Lock index 2 - locked.insert(4); // Lock index 4 - - let removed = remove_respecting_locks(&mut deque, &mut locked, 1); - assert_eq!(removed, Some(1)); - assert_eq!(deque, VecDeque::from(vec![0, 3, 2, 5, 4])); - assert!(locked.contains(&2) && locked.contains(&4)); // Both indices should still be locked - } - } -} diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 42a5f51a..9eaa6177 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -393,7 +393,9 @@ impl WindowManager { .get_mut(workspace_idx) .ok_or_eyre("no workspace at the given index")?; - workspace.locked_containers.insert(container_idx); + if let Some(container) = workspace.containers_mut().get_mut(container_idx) { + container.set_locked(true); + } } SocketMessage::UnlockMonitorWorkspaceContainer( monitor_idx, @@ -410,7 +412,9 @@ impl WindowManager { .get_mut(workspace_idx) .ok_or_eyre("no workspace at the given index")?; - workspace.locked_containers.remove(&container_idx); + if let Some(container) = workspace.containers_mut().get_mut(container_idx) { + container.set_locked(false); + } } SocketMessage::ToggleLock => self.toggle_lock()?, SocketMessage::ToggleFloat => self.toggle_float(false)?, diff --git a/komorebi/src/ring.rs b/komorebi/src/ring.rs index 04751502..9f0c8602 100644 --- a/komorebi/src/ring.rs +++ b/komorebi/src/ring.rs @@ -43,10 +43,6 @@ impl Ring { pub fn focused_mut(&mut self) -> Option<&mut T> { self.elements.get_mut(self.focused) } - - pub fn swap(&mut self, i: usize, j: usize) { - self.elements.swap(i, j); - } } macro_rules! impl_ring_elements { diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 6beecf61..5aba0499 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -347,7 +347,6 @@ impl From<&WindowManager> for State { layer: workspace.layer, floating_layer_behaviour: workspace.floating_layer_behaviour, globals: workspace.globals, - locked_containers: workspace.locked_containers.clone(), wallpaper: workspace.wallpaper.clone(), workspace_config: None, }) @@ -3192,14 +3191,10 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn toggle_lock(&mut self) -> Result<()> { let workspace = self.focused_workspace_mut()?; - let index = workspace.focused_container_idx(); - - if workspace.locked_containers().contains(&index) { - workspace.locked_containers_mut().remove(&index); - } else { - workspace.locked_containers_mut().insert(index); + if let Some(container) = workspace.focused_container_mut() { + // Toggle the locked flag + container.set_locked(!container.locked()); } - Ok(()) } @@ -5375,7 +5370,8 @@ mod tests { { // Ensure container 2 is not locked let workspace = wm.focused_workspace_mut().unwrap(); - assert!(!workspace.locked_containers().contains(&2)); + assert_eq!(workspace.focused_container_idx(), 2); + assert!(!workspace.focused_container().unwrap().locked()); } // Toggle lock on focused container @@ -5384,7 +5380,7 @@ mod tests { { // Ensure container 2 is locked let workspace = wm.focused_workspace_mut().unwrap(); - assert!(workspace.locked_containers().contains(&2)); + assert!(workspace.focused_container().unwrap().locked()); } // Toggle lock on focused container @@ -5393,7 +5389,7 @@ mod tests { { // Ensure container 2 is not locked let workspace = wm.focused_workspace_mut().unwrap(); - assert!(!workspace.locked_containers().contains(&2)); + assert!(!workspace.focused_container().unwrap().locked()); } } diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 944000d8..de7f5e3c 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeSet; use std::collections::VecDeque; use std::ffi::OsStr; use std::fmt::Display; @@ -17,7 +16,7 @@ use crate::core::Layout; use crate::core::OperationDirection; use crate::core::Rect; use crate::default_layout::LayoutOptions; -use crate::locked_deque::LockedDeque; +use crate::lockable_sequence::LockableSequence; use crate::ring::Ring; use crate::should_act; use crate::stackbar_manager; @@ -103,8 +102,6 @@ pub struct Workspace { #[getset(get_copy = "pub", get_mut = "pub", set = "pub")] pub floating_layer_behaviour: Option, #[getset(get = "pub", get_mut = "pub", set = "pub")] - pub locked_containers: BTreeSet, - #[getset(get = "pub", get_mut = "pub", set = "pub")] pub wallpaper: Option, #[serde(skip_serializing_if = "Option::is_none")] #[getset(get = "pub", set = "pub")] @@ -158,7 +155,6 @@ impl Default for Workspace { floating_layer_behaviour: Default::default(), globals: Default::default(), workspace_config: None, - locked_containers: Default::default(), wallpaper: None, } } @@ -898,10 +894,9 @@ impl Workspace { // this fn respects locked container indexes - we should use it for pretty much everything // except monocle and maximize toggles pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) -> usize { - let mut locked_containers = self.locked_containers().clone(); - let mut ld = LockedDeque::new(self.containers_mut(), &mut locked_containers); - let insertion_idx = ld.insert(idx, container); - self.locked_containers = locked_containers; + let insertion_idx = self + .containers_mut() + .insert_respecting_locks(idx, container); if insertion_idx > self.resize_dimensions().len() { self.resize_dimensions_mut().push(None); @@ -917,10 +912,7 @@ impl Workspace { // this fn respects locked container indexes - we should use it for pretty much everything // except monocle and maximize toggles pub fn remove_container_by_idx(&mut self, idx: usize) -> Option { - let mut locked_containers = self.locked_containers().clone(); - let mut ld = LockedDeque::new(self.containers_mut(), &mut locked_containers); - let container = ld.remove(idx); - self.locked_containers = locked_containers; + let container = self.containers_mut().remove_respecting_locks(idx); if idx < self.resize_dimensions().len() { self.resize_dimensions_mut().remove(idx); @@ -1634,7 +1626,7 @@ impl Workspace { } pub fn swap_containers(&mut self, i: usize, j: usize) { - self.containers.swap(i, j); + self.containers.elements_mut().swap_respecting_locks(i, j); self.focus_container(j); } @@ -1733,7 +1725,6 @@ mod tests { use super::*; use crate::container::Container; use crate::Window; - use std::collections::BTreeSet; use std::collections::HashMap; #[test] @@ -1741,20 +1732,18 @@ mod tests { let mut ws = Workspace::default(); let mut state = HashMap::new(); - let mut locked = BTreeSet::new(); - // add 3 containers + // add 4 containers for i in 0..4 { - let container = Container::default(); + let mut container = Container::default(); + if i == 3 { + container.set_locked(true); // set index 3 locked + } state.insert(i, container.id().to_string()); ws.add_container_to_back(container); } assert_eq!(ws.containers().len(), 4); - // set index 3 locked - locked.insert(3); - ws.locked_containers = locked; - // focus container at index 2 ws.focus_container(2); @@ -1786,20 +1775,17 @@ mod tests { fn test_locked_containers_remove_window() { let mut ws = Workspace::default(); - let mut locked = BTreeSet::new(); - // add 4 containers for i in 0..4 { let mut container = Container::default(); container.windows_mut().push_back(Window::from(i)); + if i == 1 { + container.set_locked(true); + } ws.add_container_to_back(container); } assert_eq!(ws.containers().len(), 4); - // set index 1 locked - locked.insert(1); - ws.locked_containers = locked; - ws.remove_window(0).unwrap(); assert_eq!(ws.containers()[0].focused_window().unwrap().hwnd, 2); // index 1 should still be the same @@ -1811,20 +1797,17 @@ mod tests { fn test_locked_containers_toggle_float() { let mut ws = Workspace::default(); - let mut locked = BTreeSet::new(); - // add 4 containers for i in 0..4 { let mut container = Container::default(); container.windows_mut().push_back(Window::from(i)); + if i == 1 { + container.set_locked(true); + } ws.add_container_to_back(container); } assert_eq!(ws.containers().len(), 4); - // set index 1 locked - locked.insert(1); - ws.locked_containers = locked; - // set index 0 focused ws.focus_container(0); @@ -1856,20 +1839,17 @@ mod tests { fn test_locked_containers_stack() { let mut ws = Workspace::default(); - let mut locked = BTreeSet::new(); - // add 6 containers for i in 0..6 { let mut container = Container::default(); container.windows_mut().push_back(Window::from(i)); + if i == 4 { + container.set_locked(true); + } ws.add_container_to_back(container); } assert_eq!(ws.containers().len(), 6); - // set index 4 locked - locked.insert(4); - ws.locked_containers = locked; - // set index 3 focused ws.focus_container(3);