gardesk/tarmac / 3b49a3e

Browse files

Add stack promotion action

Authored by espadonne
SHA
3b49a3e9ee3e285b915d69045b89e4b506f6f310
Parents
b7782ed
Tree
024c2c4

6 changed files

StatusFile+-
M tarmac/src/config/lua.rs 8 0
M tarmac/src/core/input.rs 1 0
M tarmac/src/core/state.rs 57 0
M tarmac/src/core/tree.rs 40 0
M tarmac/src/main.rs 6 0
M tarmacctl/src/main.rs 1 0
tarmac/src/config/lua.rsmodified
@@ -841,6 +841,7 @@ fn parse_action(action: &str) -> Result<Action, &'static str> {
841841
         "equalize" => Ok(Action::Equalize),
842842
         "toggle_float" => Ok(Action::ToggleFloat),
843843
         "unstack" => Ok(Action::Unstack),
844
+        "promote_stack" => Ok(Action::PromoteStack),
844845
         "workspace_next" => Ok(Action::WorkspaceNext),
845846
         "workspace_prev" => Ok(Action::WorkspacePrev),
846847
         "focus_monitor_next" => Ok(Action::FocusMonitorNext),
@@ -903,6 +904,11 @@ pub fn default_keybinds(settings: &Settings) -> Vec<LuaKeybind> {
903904
             key: Key::U,
904905
             action: Action::Unstack,
905906
         },
907
+        LuaKeybind {
908
+            modifiers: Modifiers::OPTION | Modifiers::SHIFT,
909
+            key: Key::Period,
910
+            action: Action::PromoteStack,
911
+        },
906912
         // Focus
907913
         LuaKeybind {
908914
             modifiers: m,
@@ -1169,6 +1175,7 @@ pub fn format_action(action: &Action) -> String {
11691175
         Action::WorkspacePrev => "workspace_prev".to_string(),
11701176
         Action::ToggleFloat => "toggle_float".to_string(),
11711177
         Action::Unstack => "unstack".to_string(),
1178
+        Action::PromoteStack => "promote_stack".to_string(),
11721179
         Action::ToggleSpecial(name) => format!("toggle_special {name}"),
11731180
         Action::MoveToSpecial(name) => format!("move_to_special {name}"),
11741181
         Action::FocusMonitorNext => "focus_monitor_next".to_string(),
@@ -1287,6 +1294,7 @@ mod tests {
12871294
         assert_eq!(parse_action("equalize").unwrap(), Action::Equalize);
12881295
         assert_eq!(parse_action("close").unwrap(), Action::CloseWindow);
12891296
         assert_eq!(parse_action("toggle_float").unwrap(), Action::ToggleFloat);
1297
+        assert_eq!(parse_action("promote_stack").unwrap(), Action::PromoteStack);
12901298
     }
12911299
 
12921300
     #[test]
tarmac/src/core/input.rsmodified
@@ -192,6 +192,7 @@ pub enum Action {
192192
     MoveToMonitorNext,                // Move window to next monitor
193193
     MoveToMonitorPrev,                // Move window to previous monitor
194194
     Unstack,                          // Restore a stacked subtree to its saved BSP shape
195
+    PromoteStack,                     // Move the active stack member to the top/front of the stack
195196
     Reload,                           // Hot reload config
196197
     Exit,                             // Clean exit
197198
 }
tarmac/src/core/state.rsmodified
@@ -1493,6 +1493,28 @@ impl WmState {
14931493
         }
14941494
     }
14951495
 
1496
+    pub fn promote_stack_focused(&mut self) {
1497
+        let focused = match self.effective_focused() {
1498
+            Some(f) => f,
1499
+            None => return,
1500
+        };
1501
+
1502
+        let Some(ws_idx) = self.workspaces.find_window(focused) else {
1503
+            return;
1504
+        };
1505
+
1506
+        if self
1507
+            .workspaces
1508
+            .get_mut(ws_idx)
1509
+            .tree
1510
+            .promote_stack_window(focused)
1511
+        {
1512
+            self.apply_layout();
1513
+            self.focus_window(focused);
1514
+            tracing::info!(id = focused, "promoted focused stack window to top");
1515
+        }
1516
+    }
1517
+
14961518
     pub fn click_to_focus(&mut self, x: f64, y: f64) {
14971519
         let ws = self.active_workspace();
14981520
 
@@ -3707,6 +3729,41 @@ mod tests {
37073729
         assert_eq!(state.active_workspace().focused, Some(40));
37083730
     }
37093731
 
3732
+    #[test]
3733
+    fn promote_stack_focused_moves_active_window_to_top() {
3734
+        let mut state = WmState::new();
3735
+        state.monitors = vec![Monitor {
3736
+            id: 42,
3737
+            frame: Rect::new(0.0, 0.0, 1920.0, 1080.0),
3738
+            usable_frame: Rect::new(0.0, 33.0, 1920.0, 1047.0),
3739
+            is_primary: true,
3740
+            active_workspace: 0,
3741
+        }];
3742
+        state.sync_workspace_visibility();
3743
+        let screen = state.monitors[0].usable_frame;
3744
+
3745
+        {
3746
+            let ws = state.workspaces.get_mut(0);
3747
+            ws.tree.insert_with_rect(1, None, screen);
3748
+            ws.tree.insert_with_rect(2, Some(1), screen);
3749
+            ws.tree.insert_with_rect(3, Some(2), screen);
3750
+            assert!(ws.tree.make_stack_for_window(3));
3751
+            ws.tree.set_stack_active(3);
3752
+            ws.record_focus(3);
3753
+        }
3754
+
3755
+        state.focus_direction(Direction::Left);
3756
+        assert_eq!(state.active_workspace().focused, Some(2));
3757
+
3758
+        state.promote_stack_focused();
3759
+
3760
+        assert_eq!(state.active_workspace().focused, Some(2));
3761
+        assert_eq!(
3762
+            state.active_workspace().tree.stack_info(2),
3763
+            Some((vec![2, 3], 0))
3764
+        );
3765
+    }
3766
+
37103767
     #[test]
37113768
     fn unstack_focused_restores_original_tree() {
37123769
         let mut state = WmState::new();
tarmac/src/core/tree.rsmodified
@@ -808,6 +808,34 @@ impl Node {
808808
         }
809809
     }
810810
 
811
+    pub fn promote_stack_window(&mut self, window: WindowId) -> bool {
812
+        match self {
813
+            Node::Stack {
814
+                windows,
815
+                active,
816
+                previous,
817
+            } if windows.contains(&window) => {
818
+                let Some(idx) = windows.iter().position(|wid| *wid == window) else {
819
+                    return false;
820
+                };
821
+                if idx == 0 {
822
+                    *active = 0;
823
+                    return true;
824
+                }
825
+                let displaced = windows[0];
826
+                windows.remove(idx);
827
+                windows.insert(0, window);
828
+                *active = 0;
829
+                previous.swap(window, displaced);
830
+                true
831
+            }
832
+            Node::Internal { left, right, .. } => {
833
+                left.promote_stack_window(window) || right.promote_stack_window(window)
834
+            }
835
+            _ => false,
836
+        }
837
+    }
838
+
811839
     pub fn make_stack_for_window(&mut self, window: WindowId) -> bool {
812840
         self.make_stack_for_window_impl(window)
813841
     }
@@ -1493,6 +1521,18 @@ mod tests {
14931521
         assert_eq!(g2.height, g3.height);
14941522
     }
14951523
 
1524
+    #[test]
1525
+    fn promote_stack_window_moves_target_to_top() {
1526
+        let mut tree = Node::Stack {
1527
+            windows: vec![1, 2, 3],
1528
+            active: 2,
1529
+            previous: Box::new(Node::Leaf { window: Some(3) }),
1530
+        };
1531
+
1532
+        assert!(tree.promote_stack_window(3));
1533
+        assert_eq!(tree.stack_info(3), Some((vec![3, 1, 2], 0)));
1534
+    }
1535
+
14961536
     #[test]
14971537
     fn cycle_stack_stops_at_directional_boundary() {
14981538
         let mut tree = Node::empty();
tarmac/src/main.rsmodified
@@ -251,6 +251,7 @@ fn handle_action(action: Action) {
251251
                 }
252252
                 Action::ToggleFloat => state.toggle_float(),
253253
                 Action::Unstack => state.unstack_focused(),
254
+                Action::PromoteStack => state.promote_stack_focused(),
254255
                 Action::ToggleSpecial(ref name) => state.toggle_special(name),
255256
                 Action::MoveToSpecial(ref name) => state.move_to_special(name),
256257
                 Action::FocusMonitorNext => {
@@ -333,6 +334,7 @@ gar.bind("mod+shift+q", "close")
333334
 gar.bind("mod+e", "equalize")
334335
 gar.bind("mod+shift+space", "toggle_float")
335336
 gar.bind("mod+shift+u", "unstack")
337
+gar.bind("alt+shift+.", "promote_stack")
336338
 
337339
 -- Focus
338340
 gar.bind("mod+h", "focus left")
@@ -508,6 +510,10 @@ fn process_ipc_command(
508510
                 state.unstack_focused();
509511
                 Response::ok_empty()
510512
             }
513
+            "promote_stack" => {
514
+                state.promote_stack_focused();
515
+                Response::ok_empty()
516
+            }
511517
             "workspace" => {
512518
                 if let Some(target) = request
513519
                     .args
tarmacctl/src/main.rsmodified
@@ -39,6 +39,7 @@ fn main() {
3939
         eprintln!("  close                         Close focused window");
4040
         eprintln!("  equalize                      Equalize all splits");
4141
         eprintln!("  unstack                       Restore the focused stacked subtree");
42
+        eprintln!("  promote_stack                 Move the focused stack window to the top");
4243
         eprintln!("  workspace <1-10>              Switch workspace");
4344
         eprintln!("  move-to-workspace <1-10>      Move window to workspace");
4445
         eprintln!("  toggle-floating               Toggle floating state");